0506
This commit is contained in:
		
							parent
							
								
									5f46d87528
								
							
						
					
					
						commit
						29914a4178
					
				@ -4,6 +4,7 @@ from app.models.user import db, User
 | 
				
			|||||||
from app.controllers.user import user_bp
 | 
					from app.controllers.user import user_bp
 | 
				
			||||||
from app.controllers.book import book_bp
 | 
					from app.controllers.book import book_bp
 | 
				
			||||||
from app.controllers.borrow import borrow_bp
 | 
					from app.controllers.borrow import borrow_bp
 | 
				
			||||||
 | 
					from app.controllers.inventory import inventory_bp
 | 
				
			||||||
from flask_login import LoginManager, current_user
 | 
					from flask_login import LoginManager, current_user
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,6 +49,7 @@ def create_app(config=None):
 | 
				
			|||||||
    app.register_blueprint(user_bp, url_prefix='/user')
 | 
					    app.register_blueprint(user_bp, url_prefix='/user')
 | 
				
			||||||
    app.register_blueprint(book_bp, url_prefix='/book')
 | 
					    app.register_blueprint(book_bp, url_prefix='/book')
 | 
				
			||||||
    app.register_blueprint(borrow_bp, url_prefix='/borrow')
 | 
					    app.register_blueprint(borrow_bp, url_prefix='/borrow')
 | 
				
			||||||
 | 
					    app.register_blueprint(inventory_bp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 创建数据库表
 | 
					    # 创建数据库表
 | 
				
			||||||
    with app.app_context():
 | 
					    with app.app_context():
 | 
				
			||||||
@ -132,3 +134,7 @@ def create_app(config=None):
 | 
				
			|||||||
        return s
 | 
					        return s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return app
 | 
					    return app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @app.context_processor
 | 
				
			||||||
 | 
					    def inject_now():
 | 
				
			||||||
 | 
					        return {'now': datetime.datetime.now()}
 | 
				
			||||||
 | 
				
			|||||||
@ -116,7 +116,8 @@ def book_detail(book_id):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # 如果用户是管理员,预先查询并排序借阅记录
 | 
					    # 如果用户是管理员,预先查询并排序借阅记录
 | 
				
			||||||
    borrow_records = []
 | 
					    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
 | 
					        from app.models.borrow import BorrowRecord
 | 
				
			||||||
        borrow_records = BorrowRecord.query.filter_by(book_id=book_id).order_by(BorrowRecord.borrow_date.desc()).limit(
 | 
					        borrow_records = BorrowRecord.query.filter_by(book_id=book_id).order_by(BorrowRecord.borrow_date.desc()).limit(
 | 
				
			||||||
            10).all()
 | 
					            10).all()
 | 
				
			||||||
@ -124,12 +125,13 @@ def book_detail(book_id):
 | 
				
			|||||||
    return render_template(
 | 
					    return render_template(
 | 
				
			||||||
        'book/detail.html',
 | 
					        'book/detail.html',
 | 
				
			||||||
        book=book,
 | 
					        book=book,
 | 
				
			||||||
        current_user=g.user,
 | 
					        current_user=current_user,  # 使用 flask_login 的 current_user 而不是 g.user
 | 
				
			||||||
        borrow_records=borrow_records,
 | 
					        borrow_records=borrow_records,
 | 
				
			||||||
        now=now
 | 
					        now=now
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 添加图书页面
 | 
					# 添加图书页面
 | 
				
			||||||
@book_bp.route('/add', methods=['GET', 'POST'])
 | 
					@book_bp.route('/add', methods=['GET', 'POST'])
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
@ -283,6 +285,7 @@ def edit_book(book_id):
 | 
				
			|||||||
    book = Book.query.get_or_404(book_id)
 | 
					    book = Book.query.get_or_404(book_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
 | 
					        # 获取表单数据
 | 
				
			||||||
        title = request.form.get('title')
 | 
					        title = request.form.get('title')
 | 
				
			||||||
        author = request.form.get('author')
 | 
					        author = request.form.get('author')
 | 
				
			||||||
        publisher = request.form.get('publisher')
 | 
					        publisher = request.form.get('publisher')
 | 
				
			||||||
@ -294,13 +297,72 @@ def edit_book(book_id):
 | 
				
			|||||||
        price = request.form.get('price')
 | 
					        price = request.form.get('price')
 | 
				
			||||||
        status = request.form.get('status', type=int)
 | 
					        status = request.form.get('status', type=int)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 基本验证
 | 
				
			||||||
        if not title or not author:
 | 
					        if not title or not author:
 | 
				
			||||||
            flash('书名和作者不能为空', 'danger')
 | 
					            flash('书名和作者不能为空', 'danger')
 | 
				
			||||||
            categories = Category.query.all()
 | 
					            categories = Category.query.all()
 | 
				
			||||||
            return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
 | 
					            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:
 | 
					        if new_stock != book.stock:
 | 
				
			||||||
            from app.models.inventory import InventoryLog
 | 
					            from app.models.inventory import InventoryLog
 | 
				
			||||||
            change_amount = new_stock - book.stock
 | 
					            change_amount = new_stock - book.stock
 | 
				
			||||||
@ -346,11 +408,17 @@ def edit_book(book_id):
 | 
				
			|||||||
        book.status = status
 | 
					        book.status = status
 | 
				
			||||||
        book.updated_at = datetime.datetime.now()
 | 
					        book.updated_at = datetime.datetime.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
            db.session.commit()
 | 
					            db.session.commit()
 | 
				
			||||||
 | 
					 | 
				
			||||||
            flash('图书信息更新成功', 'success')
 | 
					            flash('图书信息更新成功', 'success')
 | 
				
			||||||
            return redirect(url_for('book.book_list'))
 | 
					            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()
 | 
					    categories = Category.query.all()
 | 
				
			||||||
    return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
 | 
					    return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -622,3 +690,53 @@ def test_permissions():
 | 
				
			|||||||
    <p>是否管理员: {'是' if current_user.role_id == 1 else '否'}</p>
 | 
					    <p>是否管理员: {'是' if current_user.role_id == 1 else '否'}</p>
 | 
				
			||||||
    <p><a href="/book/admin/list">尝试访问管理页面</a></p>
 | 
					    <p><a href="/book/admin/list">尝试访问管理页面</a></p>
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 添加到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,)
 | 
				
			||||||
 | 
				
			|||||||
@ -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 flask_login import current_user, login_required
 | 
				
			||||||
from app.models.book import Book
 | 
					from app.models.book import Book
 | 
				
			||||||
from app.models.borrow import BorrowRecord
 | 
					from app.models.borrow import BorrowRecord
 | 
				
			||||||
from app.models.inventory import InventoryLog
 | 
					from app.models.inventory import InventoryLog
 | 
				
			||||||
from app.models.user import db  # 修正:从 user 模型导入 db
 | 
					from app.models.user import db, User
 | 
				
			||||||
from app.utils.auth import login_required
 | 
					 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					from app.utils.auth import admin_required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 创建借阅蓝图
 | 
					# 创建借阅蓝图
 | 
				
			||||||
borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow')
 | 
					borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow')
 | 
				
			||||||
@ -30,7 +30,7 @@ def borrow_book():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # 检查当前用户是否已借阅此书
 | 
					    # 检查当前用户是否已借阅此书
 | 
				
			||||||
    existing_borrow = BorrowRecord.query.filter_by(
 | 
					    existing_borrow = BorrowRecord.query.filter_by(
 | 
				
			||||||
        user_id=g.user.id,
 | 
					        user_id=current_user.id,
 | 
				
			||||||
        book_id=book_id,
 | 
					        book_id=book_id,
 | 
				
			||||||
        status=1  # 1表示借阅中
 | 
					        status=1  # 1表示借阅中
 | 
				
			||||||
    ).first()
 | 
					    ).first()
 | 
				
			||||||
@ -45,7 +45,7 @@ def borrow_book():
 | 
				
			|||||||
        due_date = now + datetime.timedelta(days=borrow_days)
 | 
					        due_date = now + datetime.timedelta(days=borrow_days)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        borrow_record = BorrowRecord(
 | 
					        borrow_record = BorrowRecord(
 | 
				
			||||||
            user_id=g.user.id,
 | 
					            user_id=current_user.id,
 | 
				
			||||||
            book_id=book_id,
 | 
					            book_id=book_id,
 | 
				
			||||||
            borrow_date=now,
 | 
					            borrow_date=now,
 | 
				
			||||||
            due_date=due_date,
 | 
					            due_date=due_date,
 | 
				
			||||||
@ -67,7 +67,7 @@ def borrow_book():
 | 
				
			|||||||
            change_type='借出',
 | 
					            change_type='借出',
 | 
				
			||||||
            change_amount=-1,
 | 
					            change_amount=-1,
 | 
				
			||||||
            after_stock=book.stock,
 | 
					            after_stock=book.stock,
 | 
				
			||||||
            operator_id=g.user.id,
 | 
					            operator_id=current_user.id,
 | 
				
			||||||
            remark='用户借书',
 | 
					            remark='用户借书',
 | 
				
			||||||
            changed_at=now
 | 
					            changed_at=now
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@ -101,7 +101,7 @@ def add_borrow(book_id):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # 检查是否已借阅
 | 
					    # 检查是否已借阅
 | 
				
			||||||
    existing_borrow = BorrowRecord.query.filter_by(
 | 
					    existing_borrow = BorrowRecord.query.filter_by(
 | 
				
			||||||
        user_id=current_user.id,  # 使用current_user
 | 
					        user_id=current_user.id,
 | 
				
			||||||
        book_id=book_id,
 | 
					        book_id=book_id,
 | 
				
			||||||
        status=1  # 1表示借阅中
 | 
					        status=1  # 1表示借阅中
 | 
				
			||||||
    ).first()
 | 
					    ).first()
 | 
				
			||||||
@ -118,7 +118,7 @@ def add_borrow(book_id):
 | 
				
			|||||||
        due_date = now + datetime.timedelta(days=borrow_days)
 | 
					        due_date = now + datetime.timedelta(days=borrow_days)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        borrow_record = BorrowRecord(
 | 
					        borrow_record = BorrowRecord(
 | 
				
			||||||
            user_id=current_user.id,  # 使用current_user
 | 
					            user_id=current_user.id,
 | 
				
			||||||
            book_id=book_id,
 | 
					            book_id=book_id,
 | 
				
			||||||
            borrow_date=now,
 | 
					            borrow_date=now,
 | 
				
			||||||
            due_date=due_date,
 | 
					            due_date=due_date,
 | 
				
			||||||
@ -140,7 +140,7 @@ def add_borrow(book_id):
 | 
				
			|||||||
            change_type='借出',
 | 
					            change_type='借出',
 | 
				
			||||||
            change_amount=-1,
 | 
					            change_amount=-1,
 | 
				
			||||||
            after_stock=book.stock,
 | 
					            after_stock=book.stock,
 | 
				
			||||||
            operator_id=current_user.id,  # 使用current_user
 | 
					            operator_id=current_user.id,
 | 
				
			||||||
            remark='用户借书',
 | 
					            remark='用户借书',
 | 
				
			||||||
            changed_at=now
 | 
					            changed_at=now
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@ -158,3 +158,382 @@ def add_borrow(book_id):
 | 
				
			|||||||
            'success': False,
 | 
					            'success': False,
 | 
				
			||||||
            'message': f'借阅失败: {str(e)}'
 | 
					            'message': f'借阅失败: {str(e)}'
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@borrow_bp.route('/return/<int:borrow_id>', 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/<int:borrow_id>', 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/<int:borrow_id>', 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)}'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
				
			|||||||
@ -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/<int:book_id>', 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/<int:book_id>/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)
 | 
				
			||||||
@ -405,3 +405,70 @@ def get_role_user_count(role_id):
 | 
				
			|||||||
            'message': f"查询失败: {str(e)}",
 | 
					            'message': f"查询失败: {str(e)}",
 | 
				
			||||||
            'count': 0
 | 
					            'count': 0
 | 
				
			||||||
        }), 500
 | 
					        }), 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)
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,8 @@ class Book(db.Model):
 | 
				
			|||||||
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
 | 
					    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
 | 
				
			||||||
    updated_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):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return f'<Book {self.title}>'
 | 
					        return f'<Book {self.title}>'
 | 
				
			||||||
 | 
				
			|||||||
@ -19,13 +19,14 @@ class User(db.Model, UserMixin):
 | 
				
			|||||||
    created_at = db.Column(db.DateTime, default=datetime.now)
 | 
					    created_at = db.Column(db.DateTime, default=datetime.now)
 | 
				
			||||||
    updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=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.username = username
 | 
				
			||||||
        self.set_password(password)
 | 
					        self.set_password(password)
 | 
				
			||||||
        self.email = email
 | 
					        self.email = email
 | 
				
			||||||
        self.phone = phone
 | 
					        self.phone = phone
 | 
				
			||||||
        self.nickname = nickname
 | 
					        self.nickname = nickname
 | 
				
			||||||
        self.role_id = role_id
 | 
					        self.role_id = role_id
 | 
				
			||||||
 | 
					        self.status = status  # 新增
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_active(self):
 | 
					    def is_active(self):
 | 
				
			||||||
 | 
				
			|||||||
@ -161,3 +161,24 @@ class UserService:
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            db.session.rollback()
 | 
					            db.session.rollback()
 | 
				
			||||||
            return False, f"更新失败: {str(e)}"
 | 
					            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)}'
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								app/static/covers/354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/static/covers/354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 64 KiB  | 
							
								
								
									
										424
									
								
								app/static/css/book-edit.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										424
									
								
								app/static/css/book-edit.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,424 @@
 | 
				
			|||||||
 | 
					/* ========== 优雅粉色主题 - 图书编辑系统 ========== */
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --primary-pink: #FF85A2;
 | 
				
			||||||
 | 
					    --primary-pink-hover: #FF6D8E;
 | 
				
			||||||
 | 
					    --secondary-pink: #FFC0D3;
 | 
				
			||||||
 | 
					    --accent-pink: #FF4778;
 | 
				
			||||||
 | 
					    --background-pink: #FFF5F7;
 | 
				
			||||||
 | 
					    --border-pink: #FFD6E0;
 | 
				
			||||||
 | 
					    --soft-lavender: #E2D1F9;
 | 
				
			||||||
 | 
					    --mint-green: #D0F0C0;
 | 
				
			||||||
 | 
					    --dark-text: #5D4E60;
 | 
				
			||||||
 | 
					    --medium-text: #8A7B8F;
 | 
				
			||||||
 | 
					    --light-text: #BFB5C6;
 | 
				
			||||||
 | 
					    --white: #FFFFFF;
 | 
				
			||||||
 | 
					    --shadow-sm: 0 4px 6px rgba(255, 133, 162, 0.1);
 | 
				
			||||||
 | 
					    --shadow-md: 0 6px 12px rgba(255, 133, 162, 0.15);
 | 
				
			||||||
 | 
					    --shadow-lg: 0 15px 25px rgba(255, 133, 162, 0.2);
 | 
				
			||||||
 | 
					    --border-radius-sm: 8px;
 | 
				
			||||||
 | 
					    --border-radius-md: 12px;
 | 
				
			||||||
 | 
					    --border-radius-lg: 16px;
 | 
				
			||||||
 | 
					    --transition-fast: 0.2s ease;
 | 
				
			||||||
 | 
					    --transition-base: 0.3s ease;
 | 
				
			||||||
 | 
					    --font-primary: 'Poppins', 'Helvetica Neue', sans-serif;
 | 
				
			||||||
 | 
					    --font-secondary: 'Playfair Display', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 全局样式 ========== */
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    background-color: var(--background-pink);
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					    font-family: var(--font-primary);
 | 
				
			||||||
 | 
					    line-height: 1.6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1, h2, h3, h4, h5, h6 {
 | 
				
			||||||
 | 
					    font-family: var(--font-secondary);
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: var(--accent-pink);
 | 
				
			||||||
 | 
					    transition: color var(--transition-fast);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					    color: var(--primary-pink-hover);
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-sm);
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    transition: all var(--transition-base);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-sm);
 | 
				
			||||||
 | 
					    padding: 0.5rem 1.25rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn:hover {
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					    background-color: var(--primary-pink);
 | 
				
			||||||
 | 
					    border-color: var(--primary-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover, .btn-primary:focus {
 | 
				
			||||||
 | 
					    background-color: var(--primary-pink-hover);
 | 
				
			||||||
 | 
					    border-color: var(--primary-pink-hover);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-info {
 | 
				
			||||||
 | 
					    background-color: var(--soft-lavender);
 | 
				
			||||||
 | 
					    border-color: var(--soft-lavender);
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-info:hover, .btn-info:focus {
 | 
				
			||||||
 | 
					    background-color: #D4BFF0;
 | 
				
			||||||
 | 
					    border-color: #D4BFF0;
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-secondary {
 | 
				
			||||||
 | 
					    background-color: var(--white);
 | 
				
			||||||
 | 
					    border-color: var(--border-pink);
 | 
				
			||||||
 | 
					    color: var(--medium-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-secondary:hover, .btn-secondary:focus {
 | 
				
			||||||
 | 
					    background-color: var(--border-pink);
 | 
				
			||||||
 | 
					    border-color: var(--border-pink);
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn i {
 | 
				
			||||||
 | 
					    margin-right: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 表单容器 ========== */
 | 
				
			||||||
 | 
					.book-form-container {
 | 
				
			||||||
 | 
					    max-width: 1400px;
 | 
				
			||||||
 | 
					    margin: 2rem auto;
 | 
				
			||||||
 | 
					    padding: 2rem;
 | 
				
			||||||
 | 
					    background-color: var(--white);
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-lg);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-md);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-form-container::before {
 | 
				
			||||||
 | 
					    content: '';
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 8px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, var(--primary-pink), var(--accent-pink), var(--soft-lavender));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 页面标题区域 ========== */
 | 
				
			||||||
 | 
					.page-header {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    margin-bottom: 2rem;
 | 
				
			||||||
 | 
					    padding-bottom: 1rem;
 | 
				
			||||||
 | 
					    border-bottom: 2px solid var(--secondary-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header h1 {
 | 
				
			||||||
 | 
					    font-size: 2.2rem;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    color: var(--primary-pink);
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    font-family: var(--font-secondary);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.flower-icon {
 | 
				
			||||||
 | 
					    color: var(--accent-pink);
 | 
				
			||||||
 | 
					    margin-right: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actions {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 表单元素 ========== */
 | 
				
			||||||
 | 
					.form-row {
 | 
				
			||||||
 | 
					    margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					    margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group label {
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    margin-bottom: 0.5rem;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control {
 | 
				
			||||||
 | 
					    border: 2px solid var(--border-pink);
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-sm);
 | 
				
			||||||
 | 
					    padding: 0.75rem 1rem;
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					    transition: all var(--transition-fast);
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control:focus {
 | 
				
			||||||
 | 
					    border-color: var(--primary-pink);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0.2rem rgba(255, 133, 162, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control::placeholder {
 | 
				
			||||||
 | 
					    color: var(--light-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.required {
 | 
				
			||||||
 | 
					    color: var(--accent-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select.form-control {
 | 
				
			||||||
 | 
					height: 42px; / 确保高度一致,内容不截断 */
 | 
				
			||||||
 | 
					line-height: 1.5;
 | 
				
			||||||
 | 
					padding: 8px 12px;
 | 
				
			||||||
 | 
					font-size: 0.95rem;
 | 
				
			||||||
 | 
					color: var(--dark-text);
 | 
				
			||||||
 | 
					background-color: var(--white);
 | 
				
			||||||
 | 
					border: 1px solid var(--border-pink);
 | 
				
			||||||
 | 
					border-radius: var(--border-radius-sm);
 | 
				
			||||||
 | 
					appearance: none;
 | 
				
			||||||
 | 
					-webkit-appearance: none;
 | 
				
			||||||
 | 
					-moz-appearance: none;
 | 
				
			||||||
 | 
					background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%235D4E60' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
					background-repeat: no-repeat;
 | 
				
			||||||
 | 
					background-position: right 0.75rem center;
 | 
				
			||||||
 | 
					background-size: 1rem;
 | 
				
			||||||
 | 
					transition: border-color 0.2s ease, box-shadow 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select.form-control:focus {
 | 
				
			||||||
 | 
					border-color: var(--primary-pink);
 | 
				
			||||||
 | 
					outline: none;
 | 
				
			||||||
 | 
					box-shadow: 0 0 0 0.2rem rgba(255, 133, 162, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 状态选项 / 分类样式专属修复(可选项) */
 | 
				
			||||||
 | 
					#status, #category_id {
 | 
				
			||||||
 | 
					padding-top: 8px;
 | 
				
			||||||
 | 
					padding-bottom: 8px;
 | 
				
			||||||
 | 
					font-family: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* iOS & Edge 下拉兼容优化 */
 | 
				
			||||||
 | 
					select.form-control::-ms-expand {
 | 
				
			||||||
 | 
					display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 浏览器优雅过渡体验 */
 | 
				
			||||||
 | 
					select.form-control:hover {
 | 
				
			||||||
 | 
					border-color: var(--accent-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select.form-control:disabled {
 | 
				
			||||||
 | 
					background-color: var(--background-pink);
 | 
				
			||||||
 | 
					color: var(--light-text);
 | 
				
			||||||
 | 
					cursor: not-allowed;
 | 
				
			||||||
 | 
					opacity: 0.7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					textarea.form-control {
 | 
				
			||||||
 | 
					    min-height: 150px;
 | 
				
			||||||
 | 
					    resize: vertical;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 卡片样式 ========== */
 | 
				
			||||||
 | 
					.card {
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-md);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-sm);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    transition: all var(--transition-base);
 | 
				
			||||||
 | 
					    margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					    background-color: var(--white);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card:hover {
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-header {
 | 
				
			||||||
 | 
					    background-color: var(--secondary-pink);
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					    padding: 1rem 1.5rem;
 | 
				
			||||||
 | 
					    font-family: var(--font-secondary);
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-body {
 | 
				
			||||||
 | 
					    padding: 1.5rem;
 | 
				
			||||||
 | 
					    background-color: var(--white);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 封面图片区域 ========== */
 | 
				
			||||||
 | 
					.cover-preview-container {
 | 
				
			||||||
 | 
					    padding: 1rem;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cover-preview {
 | 
				
			||||||
 | 
					    min-height: 300px;
 | 
				
			||||||
 | 
					    background-color: var(--background-pink);
 | 
				
			||||||
 | 
					    border: 2px dashed var(--secondary-pink);
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-sm);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin-bottom: 1rem;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    transition: all var(--transition-fast);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cover-preview:hover {
 | 
				
			||||||
 | 
					    border-color: var(--primary-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cover-image {
 | 
				
			||||||
 | 
					    max-width: 100%;
 | 
				
			||||||
 | 
					    max-height: 300px;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-sm);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-cover-placeholder {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    color: var(--light-text);
 | 
				
			||||||
 | 
					    padding: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-cover-placeholder i {
 | 
				
			||||||
 | 
					    font-size: 3rem;
 | 
				
			||||||
 | 
					    margin-bottom: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.upload-container {
 | 
				
			||||||
 | 
					    margin-top: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-primary {
 | 
				
			||||||
 | 
					    color: var(--primary-pink);
 | 
				
			||||||
 | 
					    border-color: var(--primary-pink);
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					    transition: all var(--transition-base);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-primary:hover, .btn-outline-primary:focus {
 | 
				
			||||||
 | 
					    background-color: var(--primary-pink);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 提交按钮区域 ========== */
 | 
				
			||||||
 | 
					.form-submit-container {
 | 
				
			||||||
 | 
					    margin-top: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-lg {
 | 
				
			||||||
 | 
					    padding: 1rem 1.5rem;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-block {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 输入组样式 */
 | 
				
			||||||
 | 
					.input-group-prepend .input-group-text {
 | 
				
			||||||
 | 
					    background-color: var(--secondary-pink);
 | 
				
			||||||
 | 
					    border-color: var(--border-pink);
 | 
				
			||||||
 | 
					    color: var(--dark-text);
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius-sm) 0 0 var(--border-radius-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 聚焦效果 */
 | 
				
			||||||
 | 
					.is-focused label {
 | 
				
			||||||
 | 
					    color: var(--primary-pink);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 动画效果 ========== */
 | 
				
			||||||
 | 
					@keyframes fadeIn {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateY(10px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateY(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-form-container {
 | 
				
			||||||
 | 
					    animation: fadeIn 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ========== 响应式样式 ========== */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .book-form-container {
 | 
				
			||||||
 | 
					        padding: 1.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        align-items: flex-start;
 | 
				
			||||||
 | 
					        gap: 1rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .actions {
 | 
				
			||||||
 | 
					        margin-top: 1rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .book-form-container {
 | 
				
			||||||
 | 
					        padding: 1rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .card-header, .card-body {
 | 
				
			||||||
 | 
					        padding: 1rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .cover-preview {
 | 
				
			||||||
 | 
					        min-height: 250px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .col-md-8, .col-md-4 {
 | 
				
			||||||
 | 
					        padding: 0 0.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-invalid {
 | 
				
			||||||
 | 
					    border-color: #dc3545;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-valid {
 | 
				
			||||||
 | 
					    border-color: #28a745;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.invalid-feedback {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					    color: #dc3545;
 | 
				
			||||||
 | 
					    font-size: 0.875rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.is-invalid ~ .invalid-feedback {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										520
									
								
								app/static/css/borrow_management.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										520
									
								
								app/static/css/borrow_management.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,520 @@
 | 
				
			|||||||
 | 
					/* borrow_management.css - Optimized for literary female audience */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Main typography and colors */
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					    background-color: #fcf8f3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-title {
 | 
				
			||||||
 | 
					    margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    border-bottom: 2px solid #d9c7b8;
 | 
				
			||||||
 | 
					    padding-bottom: 15px;
 | 
				
			||||||
 | 
					    font-family: 'Playfair Display', Georgia, serif;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-title:after {
 | 
				
			||||||
 | 
					    content: "❦";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: -12px;
 | 
				
			||||||
 | 
					    left: 50%;
 | 
				
			||||||
 | 
					    font-size: 18px;
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    background: #fcf8f3;
 | 
				
			||||||
 | 
					    padding: 0 10px;
 | 
				
			||||||
 | 
					    transform: translateX(-50%);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.container {
 | 
				
			||||||
 | 
					    background-color: #fff9f5;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 15px rgba(113, 66, 20, 0.1);
 | 
				
			||||||
 | 
					    padding: 25px;
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Tabs styling */
 | 
				
			||||||
 | 
					.tabs {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #d9c7b8;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tabs:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    bottom: -3px;
 | 
				
			||||||
 | 
					    height: 2px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, transparent, #8d6e63, transparent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab {
 | 
				
			||||||
 | 
					    padding: 12px 22px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					    border-radius: 8px 8px 0 0;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab:hover {
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    color: #704214;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.active {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    color: #f8f0e5;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.overdue-tab {
 | 
				
			||||||
 | 
					    background-color: #f9e8e8;
 | 
				
			||||||
 | 
					    color: #a15950;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.overdue-tab:hover {
 | 
				
			||||||
 | 
					    background-color: #f4d3d3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 修改 count 样式,避免与 badge 冲突 */
 | 
				
			||||||
 | 
					.count {
 | 
				
			||||||
 | 
					    background-color: rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    padding: 2px 10px;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					    margin-left: 8px;
 | 
				
			||||||
 | 
					    font-family: 'Arial', sans-serif;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    position: static;
 | 
				
			||||||
 | 
					    width: auto;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.active .count {
 | 
				
			||||||
 | 
					    background-color: rgba(255, 243, 224, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.count.overdue-count {
 | 
				
			||||||
 | 
					    background-color: rgba(161, 89, 80, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Search and filters */
 | 
				
			||||||
 | 
					.search-card {
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 8px rgba(113, 66, 20, 0.08);
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    background: linear-gradient(to bottom right, #fff, #fcf8f3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-card .card-body {
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-form {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control {
 | 
				
			||||||
 | 
					    border: 1px solid #d9c7b8;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    background-color: #fff9f5;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control:focus {
 | 
				
			||||||
 | 
					    border-color: #704214;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0.2rem rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-secondary {
 | 
				
			||||||
 | 
					    color: #704214;
 | 
				
			||||||
 | 
					    border-color: #d9c7b8;
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-secondary:hover {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    background-color: #8d6e63;
 | 
				
			||||||
 | 
					    border-color: #8d6e63;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.clear-filters {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Table styling */
 | 
				
			||||||
 | 
					.borrow-table {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border-collapse: separate;
 | 
				
			||||||
 | 
					    border-spacing: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 10px rgba(113, 66, 20, 0.05);
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table th,
 | 
				
			||||||
 | 
					.borrow-table td {
 | 
				
			||||||
 | 
					    padding: 15px 18px;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 调整借阅用户列向左偏移15px */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(3),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(3) {
 | 
				
			||||||
 | 
					    padding-right: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table th {
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 状态列调整 - 居中并确保内容显示 */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(6) {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(6) {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-item:hover {
 | 
				
			||||||
 | 
					    background-color: #f8f0e5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-item:last-child td {
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img {
 | 
				
			||||||
 | 
					    width: 65px;
 | 
				
			||||||
 | 
					    height: 90px;
 | 
				
			||||||
 | 
					    object-fit: cover;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 8px rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    border: 2px solid #fff;
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img:hover {
 | 
				
			||||||
 | 
					    transform: scale(1.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    transition: color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a:hover {
 | 
				
			||||||
 | 
					    color: #a66321;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin-top: 5px;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 修改借阅用户显示方式 */
 | 
				
			||||||
 | 
					.user-info {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    display: table-cell;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-info a {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    transition: color 0.3s ease;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-bottom: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-info a:hover {
 | 
				
			||||||
 | 
					    color: #a66321;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-nickname {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Badges and status indicators - 修复显示问题 */
 | 
				
			||||||
 | 
					.badge {
 | 
				
			||||||
 | 
					    padding: 5px 12px;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    font-size: 0.85em;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					    position: static;
 | 
				
			||||||
 | 
					    width: auto;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					    top: auto;
 | 
				
			||||||
 | 
					    right: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 给状态列的徽章额外的特异性 */
 | 
				
			||||||
 | 
					.borrow-table td .badge {
 | 
				
			||||||
 | 
					    position: static;
 | 
				
			||||||
 | 
					    width: auto;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    font-size: 0.85em;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    padding: 5px 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-primary {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-success {
 | 
				
			||||||
 | 
					    background-color: #5b8a72;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-danger {
 | 
				
			||||||
 | 
					    background-color: #a15950;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-info {
 | 
				
			||||||
 | 
					    background-color: #6a8da9;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-warning {
 | 
				
			||||||
 | 
					    background-color: #d4a76a;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-date {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin-top: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 确保状态显示正确 */
 | 
				
			||||||
 | 
					.borrow-item td:nth-child(6) span.badge {
 | 
				
			||||||
 | 
					    min-width: 80px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Buttons */
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    padding: 8px 16px;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    border-color: #704214;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover, .btn-primary:focus {
 | 
				
			||||||
 | 
					    background-color: #5d3511;
 | 
				
			||||||
 | 
					    border-color: #5d3511;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0.2rem rgba(113, 66, 20, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success {
 | 
				
			||||||
 | 
					    background-color: #5b8a72;
 | 
				
			||||||
 | 
					    border-color: #5b8a72;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success:hover, .btn-success:focus {
 | 
				
			||||||
 | 
					    background-color: #4a7561;
 | 
				
			||||||
 | 
					    border-color: #4a7561;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-warning {
 | 
				
			||||||
 | 
					    background-color: #d4a76a;
 | 
				
			||||||
 | 
					    border-color: #d4a76a;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-warning:hover, .btn-warning:focus {
 | 
				
			||||||
 | 
					    background-color: #c29355;
 | 
				
			||||||
 | 
					    border-color: #c29355;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actions .btn {
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					    margin-bottom: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-danger {
 | 
				
			||||||
 | 
					    color: #a15950 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue {
 | 
				
			||||||
 | 
					    background-color: rgba(161, 89, 80, 0.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Empty states */
 | 
				
			||||||
 | 
					.no-records {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    padding: 60px 20px;
 | 
				
			||||||
 | 
					    background-color: #f8f0e5;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    margin: 25px 0;
 | 
				
			||||||
 | 
					    border: 1px dashed #d9c7b8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-icon {
 | 
				
			||||||
 | 
					    font-size: 4.5em;
 | 
				
			||||||
 | 
					    color: #d9c7b8;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-text {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Pagination */
 | 
				
			||||||
 | 
					.pagination-container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin-top: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    border-color: #e8d9cb;
 | 
				
			||||||
 | 
					    margin: 0 3px;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-item.active .page-link {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    border-color: #704214;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link:hover {
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Modal customization */
 | 
				
			||||||
 | 
					.modal-content {
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 20px rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    background-color: #fff9f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-header {
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    border-radius: 8px 8px 0 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-title {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-footer {
 | 
				
			||||||
 | 
					    border-top: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Decorative elements */
 | 
				
			||||||
 | 
					.container:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    width: 150px;
 | 
				
			||||||
 | 
					    height: 150px;
 | 
				
			||||||
 | 
					    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cpath fill='%23d9c7b8' fill-opacity='0.2' d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
					    opacity: 0.3;
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Responsive design */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .tabs {
 | 
				
			||||||
 | 
					        flex-wrap: wrap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .tab {
 | 
				
			||||||
 | 
					        margin-bottom: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .tabs {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        border-bottom: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .tab {
 | 
				
			||||||
 | 
					        border-radius: 8px;
 | 
				
			||||||
 | 
					        margin-right: 0;
 | 
				
			||||||
 | 
					        margin-bottom: 8px;
 | 
				
			||||||
 | 
					        border: 1px solid #d9c7b8;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .borrow-table {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        overflow-x: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-cover img {
 | 
				
			||||||
 | 
					        width: 50px;
 | 
				
			||||||
 | 
					        height: 70px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-card .row {
 | 
				
			||||||
 | 
					        margin-bottom: 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										860
									
								
								app/static/css/browse.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										860
									
								
								app/static/css/browse.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,860 @@
 | 
				
			|||||||
 | 
					/* 图书浏览页面样式 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 全局容器 */
 | 
				
			||||||
 | 
					.browse-container {
 | 
				
			||||||
 | 
					    padding: 24px;
 | 
				
			||||||
 | 
					    background-color: #f6f9fc;
 | 
				
			||||||
 | 
					    min-height: calc(100vh - 60px);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 装饰气泡 */
 | 
				
			||||||
 | 
					.bubble {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: -50px;
 | 
				
			||||||
 | 
					    background-color: rgba(221, 236, 255, 0.4);
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					    animation: bubble 25s infinite ease-in;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes bubble {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: translateY(100%) scale(0);
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    50% {
 | 
				
			||||||
 | 
					        opacity: 0.6;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: translateY(-100vh) scale(1);
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 为页面添加15个泡泡 */
 | 
				
			||||||
 | 
					.bubble:nth-child(1) { left: 5%; width: 30px; height: 30px; animation-duration: 20s; animation-delay: 0s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(2) { left: 15%; width: 20px; height: 20px; animation-duration: 18s; animation-delay: 1s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(3) { left: 25%; width: 25px; height: 25px; animation-duration: 16s; animation-delay: 2s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(4) { left: 35%; width: 15px; height: 15px; animation-duration: 15s; animation-delay: 0.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(5) { left: 45%; width: 30px; height: 30px; animation-duration: 14s; animation-delay: 3s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(6) { left: 55%; width: 20px; height: 20px; animation-duration: 13s; animation-delay: 2.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(7) { left: 65%; width: 25px; height: 25px; animation-duration: 12s; animation-delay: 1.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(8) { left: 75%; width: 15px; height: 15px; animation-duration: 11s; animation-delay: 4s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(9) { left: 85%; width: 30px; height: 30px; animation-duration: 10s; animation-delay: 3.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(10) { left: 10%; width: 18px; height: 18px; animation-duration: 19s; animation-delay: 0.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(11) { left: 20%; width: 22px; height: 22px; animation-duration: 17s; animation-delay: 2.5s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(12) { left: 30%; width: 28px; height: 28px; animation-duration: 16s; animation-delay: 1.2s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(13) { left: 40%; width: 17px; height: 17px; animation-duration: 15s; animation-delay: 3.7s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(14) { left: 60%; width: 23px; height: 23px; animation-duration: 13s; animation-delay: 2.1s; }
 | 
				
			||||||
 | 
					.bubble:nth-child(15) { left: 80%; width: 19px; height: 19px; animation-duration: 12s; animation-delay: 1.7s; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 页面标题部分 */
 | 
				
			||||||
 | 
					.page-header {
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header h1 {
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    font-size: 2.2rem;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					    -webkit-background-clip: text;
 | 
				
			||||||
 | 
					    -webkit-text-fill-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.welcome-text {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.welcome-text strong {
 | 
				
			||||||
 | 
					    color: #764ba2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 过滤和搜索部分 */
 | 
				
			||||||
 | 
					.filter-section {
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-form {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    gap: 16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-row {
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-group {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    max-width: 800px;
 | 
				
			||||||
 | 
					    margin: 0 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-group .form-control {
 | 
				
			||||||
 | 
					    border: 1px solid #e4e7eb;
 | 
				
			||||||
 | 
					    border-right: none;
 | 
				
			||||||
 | 
					    border-radius: 25px 0 0 25px;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    height: 46px;
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					    box-shadow: none;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-group .form-control:focus {
 | 
				
			||||||
 | 
					    outline: none;
 | 
				
			||||||
 | 
					    border-color: #a3bffa;
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-group .btn {
 | 
				
			||||||
 | 
					    border-radius: 0 25px 25px 0;
 | 
				
			||||||
 | 
					    width: 46px;
 | 
				
			||||||
 | 
					    height: 46px;
 | 
				
			||||||
 | 
					    min-width: 46px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin-left: -1px;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11);
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-group .btn:hover {
 | 
				
			||||||
 | 
					    transform: translateY(-1px);
 | 
				
			||||||
 | 
					    box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.filter-row {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 15px;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filters {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    flex: 2;
 | 
				
			||||||
 | 
					    min-width: 180px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-toggle {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 42px;
 | 
				
			||||||
 | 
					    background: #f7fafc;
 | 
				
			||||||
 | 
					    border: 1px solid #e4e7eb;
 | 
				
			||||||
 | 
					    border-radius: 25px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-toggle:hover {
 | 
				
			||||||
 | 
					    background: #edf2f7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-toggle i.fa-chevron-down {
 | 
				
			||||||
 | 
					    margin-left: 8px;
 | 
				
			||||||
 | 
					    transition: transform 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-toggle.active i.fa-chevron-down {
 | 
				
			||||||
 | 
					    transform: rotate(180deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-dropdown {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 100%;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    margin-top: 8px;
 | 
				
			||||||
 | 
					    background: white;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    z-index: 100;
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					    max-height: 300px;
 | 
				
			||||||
 | 
					    overflow-y: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-filter-dropdown.show {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    animation: fadeIn 0.2s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes fadeIn {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateY(-10px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateY(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-item {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    padding: 10px 15px;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					    transition: all 0.2s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-item:hover {
 | 
				
			||||||
 | 
					    background: #f7fafc;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-item.active {
 | 
				
			||||||
 | 
					    background: #ebf4ff;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.category-item i {
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.filter-group {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    min-width: 130px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.filter-section .form-control {
 | 
				
			||||||
 | 
					    border: 1px solid #e4e7eb;
 | 
				
			||||||
 | 
					    border-radius: 25px;
 | 
				
			||||||
 | 
					    height: 42px;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					    appearance: none;
 | 
				
			||||||
 | 
					    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23667eea' d='M6 8.825L1.175 4 2.238 2.938 6 6.7 9.763 2.937 10.825 4z'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: right 15px center;
 | 
				
			||||||
 | 
					    background-size: 12px;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.filter-section .form-control:focus {
 | 
				
			||||||
 | 
					    outline: none;
 | 
				
			||||||
 | 
					    border-color: #a3bffa;
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 图书统计显示 */
 | 
				
			||||||
 | 
					.browse-stats {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 16px;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stat-item {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    background: white;
 | 
				
			||||||
 | 
					    padding: 12px 20px;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    min-width: 160px;
 | 
				
			||||||
 | 
					    max-width: 240px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stat-item i {
 | 
				
			||||||
 | 
					    font-size: 24px;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					    margin-right: 15px;
 | 
				
			||||||
 | 
					    background: #ebf4ff;
 | 
				
			||||||
 | 
					    width: 45px;
 | 
				
			||||||
 | 
					    height: 45px;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stat-content {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stat-value {
 | 
				
			||||||
 | 
					    font-size: 1.25rem;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stat-label {
 | 
				
			||||||
 | 
					    font-size: 0.875rem;
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-results {
 | 
				
			||||||
 | 
					    flex: 2;
 | 
				
			||||||
 | 
					    padding: 12px 20px;
 | 
				
			||||||
 | 
					    background: #ebf4ff;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 图书网格布局 */
 | 
				
			||||||
 | 
					.books-grid {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
 | 
				
			||||||
 | 
					    gap: 25px;
 | 
				
			||||||
 | 
					    margin-bottom: 40px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 图书卡片样式 */
 | 
				
			||||||
 | 
					.book-card {
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transform: translateY(20px);
 | 
				
			||||||
 | 
					    animation: fadeInUp 0.5s forwards;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes fadeInUp {
 | 
				
			||||||
 | 
					    to {
 | 
				
			||||||
 | 
					        opacity: 1;
 | 
				
			||||||
 | 
					        transform: translateY(0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(1) { animation-delay: 0.1s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(2) { animation-delay: 0.15s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(3) { animation-delay: 0.2s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(4) { animation-delay: 0.25s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(5) { animation-delay: 0.3s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(6) { animation-delay: 0.35s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(7) { animation-delay: 0.4s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(8) { animation-delay: 0.45s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(9) { animation-delay: 0.5s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(10) { animation-delay: 0.55s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(11) { animation-delay: 0.6s; }
 | 
				
			||||||
 | 
					.books-grid .book-card:nth-child(12) { animation-delay: 0.65s; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-card:hover {
 | 
				
			||||||
 | 
					    transform: translateY(-8px);
 | 
				
			||||||
 | 
					    box-shadow: 0 15px 30px rgba(0, 0, 0, 0.12);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover {
 | 
				
			||||||
 | 
					    height: 240px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    object-fit: cover;
 | 
				
			||||||
 | 
					    transition: transform 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-card:hover .book-cover img {
 | 
				
			||||||
 | 
					    transform: scale(1.08);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cover-overlay {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					    background: linear-gradient(to bottom, rgba(0,0,0,0) 50%, rgba(0,0,0,0.5) 100%);
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-ribbon {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 10px;
 | 
				
			||||||
 | 
					    right: -30px;
 | 
				
			||||||
 | 
					    transform: rotate(45deg);
 | 
				
			||||||
 | 
					    width: 120px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-ribbon span {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 5px 0;
 | 
				
			||||||
 | 
					    font-size: 0.75rem;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    text-transform: uppercase;
 | 
				
			||||||
 | 
					    letter-spacing: 1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-ribbon .available {
 | 
				
			||||||
 | 
					    background-color: #4caf50;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-ribbon .unavailable {
 | 
				
			||||||
 | 
					    background-color: #f44336;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-cover {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, #f6f9fc 0%, #e9ecef 100%);
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-cover i {
 | 
				
			||||||
 | 
					    font-size: 40px;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-info {
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    margin: 0 0 8px;
 | 
				
			||||||
 | 
					    line-height: 1.4;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
 | 
					    display: -webkit-box;
 | 
				
			||||||
 | 
					    -webkit-line-clamp: 2;
 | 
				
			||||||
 | 
					    -webkit-box-orient: vertical;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author {
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					    margin-bottom: 15px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    text-overflow: ellipsis;
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-meta {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 8px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-category {
 | 
				
			||||||
 | 
					    padding: 5px 10px;
 | 
				
			||||||
 | 
					    background-color: #ebf4ff;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    font-size: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-year {
 | 
				
			||||||
 | 
					    padding: 5px 10px;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    font-size: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-actions {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr 1fr;
 | 
				
			||||||
 | 
					    gap: 10px;
 | 
				
			||||||
 | 
					    margin-top: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-actions a, .book-actions button {
 | 
				
			||||||
 | 
					    padding: 10px 12px;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    gap: 8px;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-detail {
 | 
				
			||||||
 | 
					    background-color: #e9ecef;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-detail:hover {
 | 
				
			||||||
 | 
					    background-color: #dee2e6;
 | 
				
			||||||
 | 
					    color: #2d3748;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-borrow {
 | 
				
			||||||
 | 
					    background-color: #667eea;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-borrow:hover {
 | 
				
			||||||
 | 
					    background-color: #5a67d8;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 10px rgba(102, 126, 234, 0.4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-borrow.disabled {
 | 
				
			||||||
 | 
					    background-color: #cbd5e0;
 | 
				
			||||||
 | 
					    color: #718096;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 无图书状态 */
 | 
				
			||||||
 | 
					.no-books {
 | 
				
			||||||
 | 
					    grid-column: 1 / -1;
 | 
				
			||||||
 | 
					    padding: 50px 30px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    border-radius: 16px;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-books-img {
 | 
				
			||||||
 | 
					    max-width: 200px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-books h3 {
 | 
				
			||||||
 | 
					    font-size: 1.25rem;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    margin: 0 0 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-books p {
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-reset-search {
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    gap: 8px;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    background-color: #667eea;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-reset-search:hover {
 | 
				
			||||||
 | 
					    background-color: #5a67d8;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 10px rgba(102, 126, 234, 0.4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页容器 */
 | 
				
			||||||
 | 
					.pagination-container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 0 0 15px 0;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    border-radius: 30px;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-item {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    min-width: 40px;
 | 
				
			||||||
 | 
					    height: 40px;
 | 
				
			||||||
 | 
					    padding: 0 15px;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    transition: all 0.2s;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link:hover {
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-item.active .page-link {
 | 
				
			||||||
 | 
					    background-color: #667eea;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    box-shadow: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-item.disabled .page-link {
 | 
				
			||||||
 | 
					    color: #cbd5e0;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination-info {
 | 
				
			||||||
 | 
					    color: #8492a6;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 模态框样式优化 */
 | 
				
			||||||
 | 
					.modal-content {
 | 
				
			||||||
 | 
					    border-radius: 16px;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-header {
 | 
				
			||||||
 | 
					    padding: 20px 25px;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #e2e8f0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-title {
 | 
				
			||||||
 | 
					    color: #3c4858;
 | 
				
			||||||
 | 
					    font-size: 1.2rem;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-body {
 | 
				
			||||||
 | 
					    padding: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-footer {
 | 
				
			||||||
 | 
					    padding: 15px 25px;
 | 
				
			||||||
 | 
					    border-top: 1px solid #e2e8f0;
 | 
				
			||||||
 | 
					    background-color: #f7fafc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-info {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    padding: 12px 16px;
 | 
				
			||||||
 | 
					    background-color: #ebf8ff;
 | 
				
			||||||
 | 
					    border-left: 4px solid #4299e1;
 | 
				
			||||||
 | 
					    color: #2b6cb0;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .close {
 | 
				
			||||||
 | 
					    font-size: 1.5rem;
 | 
				
			||||||
 | 
					    color: #a0aec0;
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					    text-shadow: none;
 | 
				
			||||||
 | 
					    transition: all 0.2s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .close:hover {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    color: #667eea;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .btn {
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    transition: all 0.3s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .btn-secondary {
 | 
				
			||||||
 | 
					    background-color: #e2e8f0;
 | 
				
			||||||
 | 
					    color: #4a5568;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .btn-secondary:hover {
 | 
				
			||||||
 | 
					    background-color: #cbd5e0;
 | 
				
			||||||
 | 
					    color: #2d3748;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .btn-primary {
 | 
				
			||||||
 | 
					    background-color: #667eea;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal .btn-primary:hover {
 | 
				
			||||||
 | 
					    background-color: #5a67d8;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 10px rgba(102, 126, 234, 0.4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式调整 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .filter-row {
 | 
				
			||||||
 | 
					        flex-wrap: wrap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .category-filters {
 | 
				
			||||||
 | 
					        flex: 1 0 100%;
 | 
				
			||||||
 | 
					        margin-bottom: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .filter-group {
 | 
				
			||||||
 | 
					        flex: 1 0 180px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .browse-container {
 | 
				
			||||||
 | 
					        padding: 16px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header {
 | 
				
			||||||
 | 
					        text-align: left;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .filter-section {
 | 
				
			||||||
 | 
					        padding: 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-form {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        gap: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-group {
 | 
				
			||||||
 | 
					        max-width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .filter-row {
 | 
				
			||||||
 | 
					        gap: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .books-grid {
 | 
				
			||||||
 | 
					        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
 | 
				
			||||||
 | 
					        gap: 16px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .stat-item {
 | 
				
			||||||
 | 
					        min-width: 130px;
 | 
				
			||||||
 | 
					        padding: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .stat-item i {
 | 
				
			||||||
 | 
					        width: 35px;
 | 
				
			||||||
 | 
					        height: 35px;
 | 
				
			||||||
 | 
					        font-size: 18px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-results {
 | 
				
			||||||
 | 
					        padding: 10px;
 | 
				
			||||||
 | 
					        font-size: 0.9rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 576px) {
 | 
				
			||||||
 | 
					    .books-grid {
 | 
				
			||||||
 | 
					        grid-template-columns: 1fr 1fr;
 | 
				
			||||||
 | 
					        gap: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-cover {
 | 
				
			||||||
 | 
					        height: 180px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-info {
 | 
				
			||||||
 | 
					        padding: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-title {
 | 
				
			||||||
 | 
					        font-size: 0.9rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-author {
 | 
				
			||||||
 | 
					        font-size: 0.8rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-actions {
 | 
				
			||||||
 | 
					        grid-template-columns: 1fr;
 | 
				
			||||||
 | 
					        gap: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .browse-stats {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        align-items: stretch;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .stat-item {
 | 
				
			||||||
 | 
					        max-width: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .search-results {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										461
									
								
								app/static/css/inventory-adjust.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								app/static/css/inventory-adjust.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,461 @@
 | 
				
			|||||||
 | 
					/* 迪士尼主题库存管理页面样式 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 基础样式 */
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    background-color: #f9f7ff;
 | 
				
			||||||
 | 
					    font-family: 'Arial Rounded MT Bold', 'Helvetica Neue', Arial, sans-serif;
 | 
				
			||||||
 | 
					    color: #3d4c65;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 迪士尼风格卡片 */
 | 
				
			||||||
 | 
					.disney-inventory-card {
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    margin-bottom: 40px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    padding: 2px;
 | 
				
			||||||
 | 
					    border: 3px solid #f0e6fa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-inventory-card:hover {
 | 
				
			||||||
 | 
					    box-shadow: 0 15px 30px rgba(110, 125, 249, 0.2);
 | 
				
			||||||
 | 
					    transform: translateY(-5px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 迪士尼装饰元素 */
 | 
				
			||||||
 | 
					.disney-decoration {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 60px;
 | 
				
			||||||
 | 
					    height: 60px;
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.top-left {
 | 
				
			||||||
 | 
					    top: 10px;
 | 
				
			||||||
 | 
					    left: 10px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/Vyo9IF4.png'); /* 替换为迪士尼星星图标URL */
 | 
				
			||||||
 | 
					    transform: rotate(-15deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.top-right {
 | 
				
			||||||
 | 
					    top: 10px;
 | 
				
			||||||
 | 
					    right: 10px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/pLRUYhb.png'); /* 替换为迪士尼魔法棒图标URL */
 | 
				
			||||||
 | 
					    transform: rotate(15deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bottom-left {
 | 
				
			||||||
 | 
					    bottom: 10px;
 | 
				
			||||||
 | 
					    left: 10px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/KkMfwWv.png'); /* 替换为迪士尼城堡图标URL */
 | 
				
			||||||
 | 
					    transform: rotate(-5deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bottom-right {
 | 
				
			||||||
 | 
					    bottom: 10px;
 | 
				
			||||||
 | 
					    right: 10px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/TcA6PL2.png'); /* 替换为迪士尼皇冠图标URL */
 | 
				
			||||||
 | 
					    transform: rotate(5deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 米奇耳朵标题装饰 */
 | 
				
			||||||
 | 
					.card-header-disney {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #e4c1f9, #d4a5ff);
 | 
				
			||||||
 | 
					    color: #512b81;
 | 
				
			||||||
 | 
					    padding: 1.8rem 1.5rem 1.5rem;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    border-radius: 18px 18px 0 0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mickey-ears {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -25px;
 | 
				
			||||||
 | 
					    left: 50%;
 | 
				
			||||||
 | 
					    transform: translateX(-50%);
 | 
				
			||||||
 | 
					    width: 80px;
 | 
				
			||||||
 | 
					    height: 40px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/pCPQoZx.png'); /* 替换为米奇耳朵图标URL */
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片内容 */
 | 
				
			||||||
 | 
					.card-body-disney {
 | 
				
			||||||
 | 
					    padding: 2.5rem;
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    border-radius: 0 0 18px 18px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书籍封面 */
 | 
				
			||||||
 | 
					.book-cover-container {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: flex-start;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover {
 | 
				
			||||||
 | 
					    max-height: 300px;
 | 
				
			||||||
 | 
					    width: auto;
 | 
				
			||||||
 | 
					    object-fit: contain;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					    border: 3px solid #f9f0ff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover:hover {
 | 
				
			||||||
 | 
					    transform: scale(1.03);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-sparkles {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/8vZuwlG.png'); /* 替换为迪士尼闪光效果URL */
 | 
				
			||||||
 | 
					    background-size: 200px;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: center;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: opacity 0.5s ease;
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover:hover + .disney-sparkles {
 | 
				
			||||||
 | 
					    opacity: 0.7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书籍详情 */
 | 
				
			||||||
 | 
					.book-details {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    color: #5e35b1;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    margin-bottom: 1.8rem;
 | 
				
			||||||
 | 
					    font-size: 1.8rem;
 | 
				
			||||||
 | 
					    border-bottom: 3px dotted #e1bee7;
 | 
				
			||||||
 | 
					    padding-bottom: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-info {
 | 
				
			||||||
 | 
					    font-size: 1.05rem;
 | 
				
			||||||
 | 
					    color: #424242;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-info p {
 | 
				
			||||||
 | 
					    margin-bottom: 1rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 迪士尼图标 */
 | 
				
			||||||
 | 
					.disney-icon {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    width: 28px;
 | 
				
			||||||
 | 
					    height: 28px;
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-position: center;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.author-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/2K5qpgQ.png'); /* 替换为米妮图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.publisher-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/YKhKVT7.png'); /* 替换为唐老鸭图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.isbn-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/ioaQTBM.png'); /* 替换为高飞图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/D0jRTKX.png'); /* 替换为奇奇蒂蒂图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.type-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/xgQriQn.png'); /* 替换为米奇图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.amount-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/ioaQTBM.png'); /* 替换为高飞图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.remark-icon {
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/2K5qpgQ.png'); /* 替换为米妮图标URL */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 库存状态标签 */
 | 
				
			||||||
 | 
					.stock-badge {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    padding: 0.35em 0.9em;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    margin-left: 8px;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.high-stock {
 | 
				
			||||||
 | 
					    background-color: #e0f7fa;
 | 
				
			||||||
 | 
					    color: #0097a7;
 | 
				
			||||||
 | 
					    border: 2px solid #80deea;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.low-stock {
 | 
				
			||||||
 | 
					    background-color: #fff8e1;
 | 
				
			||||||
 | 
					    color: #ff8f00;
 | 
				
			||||||
 | 
					    border: 2px solid #ffe082;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.out-stock {
 | 
				
			||||||
 | 
					    background-color: #ffebee;
 | 
				
			||||||
 | 
					    color: #c62828;
 | 
				
			||||||
 | 
					    border: 2px solid #ef9a9a;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表单容器 */
 | 
				
			||||||
 | 
					.form-container {
 | 
				
			||||||
 | 
					    background-color: #f8f4ff;
 | 
				
			||||||
 | 
					    padding: 2rem;
 | 
				
			||||||
 | 
					    border-radius: 15px;
 | 
				
			||||||
 | 
					    margin-top: 2rem;
 | 
				
			||||||
 | 
					    border: 2px dashed #d1c4e9;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表单标签 */
 | 
				
			||||||
 | 
					.disney-label {
 | 
				
			||||||
 | 
					    color: #5e35b1;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    margin-bottom: 0.8rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 自定义表单控件 */
 | 
				
			||||||
 | 
					.disney-select,
 | 
				
			||||||
 | 
					.disney-input,
 | 
				
			||||||
 | 
					.disney-textarea {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 0.8rem 1rem;
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    font-weight: 400;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    color: #495057;
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    background-clip: padding-box;
 | 
				
			||||||
 | 
					    border: 2px solid #d1c4e9;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-select:focus,
 | 
				
			||||||
 | 
					.disney-input:focus,
 | 
				
			||||||
 | 
					.disney-textarea:focus {
 | 
				
			||||||
 | 
					    border-color: #9575cd;
 | 
				
			||||||
 | 
					    outline: 0;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0.2rem rgba(149, 117, 205, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 确保下拉菜单选项可见 */
 | 
				
			||||||
 | 
					.disney-select option {
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    color: #495057;
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 库存提示 */
 | 
				
			||||||
 | 
					.stock-hint {
 | 
				
			||||||
 | 
					    color: #757575;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    margin-top: 0.6rem;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-hint.warning {
 | 
				
			||||||
 | 
					    color: #ff8f00;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-hint.danger {
 | 
				
			||||||
 | 
					    color: #c62828;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 按钮样式 */
 | 
				
			||||||
 | 
					.button-group {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: flex-end;
 | 
				
			||||||
 | 
					    gap: 15px;
 | 
				
			||||||
 | 
					    margin-top: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					    padding: 0.7rem 2rem;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-cancel-btn {
 | 
				
			||||||
 | 
					    background-color: #f3e5f5;
 | 
				
			||||||
 | 
					    color: #6a1b9a;
 | 
				
			||||||
 | 
					    border: 2px solid #ce93d8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-cancel-btn:hover {
 | 
				
			||||||
 | 
					    background-color: #e1bee7;
 | 
				
			||||||
 | 
					    color: #4a148c;
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-confirm-btn {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #7e57c2, #5e35b1);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-confirm-btn:hover {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #673ab7, #4527a0);
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					    box-shadow: 0 7px 15px rgba(103, 58, 183, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-confirm-btn:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -10px;
 | 
				
			||||||
 | 
					    left: -20px;
 | 
				
			||||||
 | 
					    width: 40px;
 | 
				
			||||||
 | 
					    height: 40px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/8vZuwlG.png'); /* 替换为迪士尼魔法效果URL */
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: all 0.5s ease;
 | 
				
			||||||
 | 
					    transform: scale(0.5);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-confirm-btn:hover:before {
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					    transform: scale(1) rotate(45deg);
 | 
				
			||||||
 | 
					    top: -5px;
 | 
				
			||||||
 | 
					    left: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式调整 */
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .book-cover-container {
 | 
				
			||||||
 | 
					        margin-bottom: 30px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-cover {
 | 
				
			||||||
 | 
					        max-height: 250px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-title {
 | 
				
			||||||
 | 
					        text-align: center;
 | 
				
			||||||
 | 
					        font-size: 1.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .disney-decoration {
 | 
				
			||||||
 | 
					        width: 40px;
 | 
				
			||||||
 | 
					        height: 40px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .button-group {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .btn {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        margin-bottom: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .card-header-disney,
 | 
				
			||||||
 | 
					    .card-body-disney {
 | 
				
			||||||
 | 
					        padding: 1.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表单元素聚焦效果 */
 | 
				
			||||||
 | 
					.form-group.focused {
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.focused .disney-label {
 | 
				
			||||||
 | 
					    color: #7e57c2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 提交动画 */
 | 
				
			||||||
 | 
					.disney-inventory-card.submitting {
 | 
				
			||||||
 | 
					    animation: submitPulse 1s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes submitPulse {
 | 
				
			||||||
 | 
					    0% { transform: scale(1); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); }
 | 
				
			||||||
 | 
					    50% { transform: scale(1.02); box-shadow: 0 15px 35px rgba(126, 87, 194, 0.3); }
 | 
				
			||||||
 | 
					    100% { transform: scale(1); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 确认按钮动画 */
 | 
				
			||||||
 | 
					.disney-confirm-btn.active {
 | 
				
			||||||
 | 
					    animation: btnPulse 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes btnPulse {
 | 
				
			||||||
 | 
					    0% { transform: scale(1); }
 | 
				
			||||||
 | 
					    50% { transform: scale(1.05); }
 | 
				
			||||||
 | 
					    100% { transform: scale(1); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表单过渡效果 */
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-select,
 | 
				
			||||||
 | 
					.disney-input,
 | 
				
			||||||
 | 
					.disney-textarea {
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 闪光效果持续时间 */
 | 
				
			||||||
 | 
					.disney-sparkles {
 | 
				
			||||||
 | 
					    transition: opacity 0.8s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										715
									
								
								app/static/css/inventory-book-logs.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										715
									
								
								app/static/css/inventory-book-logs.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,715 @@
 | 
				
			|||||||
 | 
					/* 冰雪奇缘主题库存日志页面样式 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 基础背景与字体 */
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: 'Arial Rounded MT Bold', 'Helvetica Neue', Arial, sans-serif;
 | 
				
			||||||
 | 
					    background-color: #e6f2ff;
 | 
				
			||||||
 | 
					    color: #2c3e50;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 冰雪背景 */
 | 
				
			||||||
 | 
					.frozen-background {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    min-height: 100vh;
 | 
				
			||||||
 | 
					    padding: 30px 0 50px;
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, #e4f1fe, #d4e6fb, #c9e0ff);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 雪花效果 */
 | 
				
			||||||
 | 
					.snowflakes {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.snowflake {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    font-size: 1.5em;
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					    top: -20px;
 | 
				
			||||||
 | 
					    animation: snowfall linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.snowflake:nth-child(1) { left: 10%; animation-duration: 15s; animation-delay: 0s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(2) { left: 20%; animation-duration: 12s; animation-delay: 1s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(3) { left: 30%; animation-duration: 13s; animation-delay: 2s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(4) { left: 40%; animation-duration: 10s; animation-delay: 0s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(5) { left: 50%; animation-duration: 16s; animation-delay: 3s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(6) { left: 60%; animation-duration: 14s; animation-delay: 1s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(7) { left: 70%; animation-duration: 12s; animation-delay: 0s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(8) { left: 80%; animation-duration: 15s; animation-delay: 2s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(9) { left: 90%; animation-duration: 13s; animation-delay: 1s; }
 | 
				
			||||||
 | 
					.snowflake:nth-child(10) { left: 95%; animation-duration: 14s; animation-delay: 3s; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes snowfall {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: translateY(0) rotate(0deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: translateY(100vh) rotate(360deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 冰雪主题卡片 */
 | 
				
			||||||
 | 
					.frozen-card {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    background-color: rgba(255, 255, 255, 0.85);
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    box-shadow: 0 10px 30px rgba(79, 149, 255, 0.2);
 | 
				
			||||||
 | 
					    backdrop-filter: blur(10px);
 | 
				
			||||||
 | 
					    border: 2px solid #e1f0ff;
 | 
				
			||||||
 | 
					    margin-bottom: 40px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 城堡装饰 */
 | 
				
			||||||
 | 
					.castle-decoration {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -40px;
 | 
				
			||||||
 | 
					    right: 30px;
 | 
				
			||||||
 | 
					    width: 120px;
 | 
				
			||||||
 | 
					    height: 120px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/KkMfwWv.png');
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    opacity: 0.6;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					    transform: rotate(10deg);
 | 
				
			||||||
 | 
					    filter: hue-rotate(190deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片标题栏 */
 | 
				
			||||||
 | 
					.card-header-frozen {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #7AB6FF, #94C5FF);
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    padding: 1.5rem;
 | 
				
			||||||
 | 
					    border-radius: 18px 18px 0 0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    text-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-header-frozen h4 {
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    font-size: 1.6rem;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-header-frozen i {
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 冰晶装饰 */
 | 
				
			||||||
 | 
					.ice-crystal {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 50px;
 | 
				
			||||||
 | 
					    height: 50px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/8vZuwlG.png');
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    filter: brightness(1.2) hue-rotate(190deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ice-crystal.left {
 | 
				
			||||||
 | 
					    left: 20px;
 | 
				
			||||||
 | 
					    transform: rotate(-30deg) scale(0.8);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ice-crystal.right {
 | 
				
			||||||
 | 
					    right: 20px;
 | 
				
			||||||
 | 
					    transform: rotate(30deg) scale(0.8);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片内容区 */
 | 
				
			||||||
 | 
					.card-body-frozen {
 | 
				
			||||||
 | 
					    padding: 2.5rem;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书籍基本信息区域 */
 | 
				
			||||||
 | 
					.book-info-row {
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, rgba(232, 244, 255, 0.7), rgba(216, 234, 255, 0.4));
 | 
				
			||||||
 | 
					    border-radius: 15px;
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					    margin-bottom: 30px !important;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(79, 149, 255, 0.1);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书籍封面 */
 | 
				
			||||||
 | 
					.book-cover-container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-frame {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    transform: rotate(-3deg);
 | 
				
			||||||
 | 
					    transition: transform 0.5s ease;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-frame:hover {
 | 
				
			||||||
 | 
					    transform: rotate(0deg) scale(1.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover {
 | 
				
			||||||
 | 
					    max-height: 250px;
 | 
				
			||||||
 | 
					    width: auto;
 | 
				
			||||||
 | 
					    object-fit: contain;
 | 
				
			||||||
 | 
					    border-radius: 5px;
 | 
				
			||||||
 | 
					    transform: rotate(3deg);
 | 
				
			||||||
 | 
					    transition: transform 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-frame:hover .book-cover {
 | 
				
			||||||
 | 
					    transform: rotate(0deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-glow {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    background: radial-gradient(circle at 50% 50%, rgba(173, 216, 230, 0.4), rgba(173, 216, 230, 0) 70%);
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: opacity 0.5s ease;
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-frame:hover .book-glow {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书籍详情 */
 | 
				
			||||||
 | 
					.book-details {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    color: #4169e1;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					    font-size: 1.8rem;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: -10px;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 3px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, #7AB6FF, transparent);
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-info {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
 | 
				
			||||||
 | 
					    gap: 15px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.info-item {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					    color: #34495e;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.info-item i {
 | 
				
			||||||
 | 
					    color: #7AB6FF;
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					    font-size: 1.2rem;
 | 
				
			||||||
 | 
					    width: 24px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 库存标签 */
 | 
				
			||||||
 | 
					.frozen-badge {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    padding: 0.35em 0.9em;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    margin-left: 8px;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.high-stock {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #e0f7fa, #b3e5fc);
 | 
				
			||||||
 | 
					    color: #0277bd;
 | 
				
			||||||
 | 
					    border: 1px solid #81d4fa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.low-stock {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #fff8e1, #ffecb3);
 | 
				
			||||||
 | 
					    color: #ff8f00;
 | 
				
			||||||
 | 
					    border: 1px solid #ffe082;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.out-stock {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #ffebee, #ffcdd2);
 | 
				
			||||||
 | 
					    color: #c62828;
 | 
				
			||||||
 | 
					    border: 1px solid #ef9a9a;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 历史记录区域 */
 | 
				
			||||||
 | 
					.history-section {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    margin-top: 40px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-title {
 | 
				
			||||||
 | 
					    color: #4169e1;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    font-size: 1.4rem;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-title i {
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					    color: #7AB6FF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.magic-underline {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: -8px;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 3px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, #7AB6FF, transparent);
 | 
				
			||||||
 | 
					    animation: sparkle 2s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes sparkle {
 | 
				
			||||||
 | 
					    0%, 100% { opacity: 0.5; }
 | 
				
			||||||
 | 
					    50% { opacity: 1; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 自定义表格 */
 | 
				
			||||||
 | 
					.table-container {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    margin-bottom: 30px;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(79, 149, 255, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-frozen {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-header-row {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 0.5fr 1fr 0.8fr 0.8fr 1fr 2fr 1.5fr;
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #5e81ac, #81a1c1);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.th-frozen {
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.th-frozen:not(:last-child)::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    top: 20%;
 | 
				
			||||||
 | 
					    height: 60%;
 | 
				
			||||||
 | 
					    width: 1px;
 | 
				
			||||||
 | 
					    background-color: rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-body {
 | 
				
			||||||
 | 
					    max-height: 500px;
 | 
				
			||||||
 | 
					    overflow-y: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-row {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 0.5fr 1fr 0.8fr 0.8fr 1fr 2fr 1.5fr;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ecf0f1;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-row:hover {
 | 
				
			||||||
 | 
					    background-color: #f0f8ff;
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 10px rgba(79, 149, 255, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-row::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    width: 4px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to bottom, #7AB6FF, #5e81ac);
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transition: opacity 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-row:hover::before {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.td-frozen {
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.remark-cell {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    justify-content: flex-start;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    color: #7f8c8d;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表格中的徽章 */
 | 
				
			||||||
 | 
					.operation-badge {
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    padding: 5px 12px;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.operation-badge i {
 | 
				
			||||||
 | 
					    margin-left: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.in-badge {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #e0f7fa, #b3e5fc);
 | 
				
			||||||
 | 
					    color: #0277bd;
 | 
				
			||||||
 | 
					    border: 1px solid #81d4fa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.out-badge {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #fff8e1, #ffecb3);
 | 
				
			||||||
 | 
					    color: #ff8f00;
 | 
				
			||||||
 | 
					    border: 1px solid #ffe082;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 奥拉夫空状态 */
 | 
				
			||||||
 | 
					.empty-log {
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr !important;
 | 
				
			||||||
 | 
					    height: 250px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-message {
 | 
				
			||||||
 | 
					    grid-column: span 7;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.olaf-empty {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.olaf-image {
 | 
				
			||||||
 | 
					    width: 120px;
 | 
				
			||||||
 | 
					    height: 150px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/lM0cLxb.png');
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: center;
 | 
				
			||||||
 | 
					    margin: 0 auto 15px;
 | 
				
			||||||
 | 
					    animation: olaf-wave 3s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes olaf-wave {
 | 
				
			||||||
 | 
					    0%, 100% { transform: rotate(-5deg); }
 | 
				
			||||||
 | 
					    50% { transform: rotate(5deg); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.olaf-empty p {
 | 
				
			||||||
 | 
					    font-size: 1.2rem;
 | 
				
			||||||
 | 
					    color: #7f8c8d;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 特殊的行样式 */
 | 
				
			||||||
 | 
					.log-entry[data-type="in"] {
 | 
				
			||||||
 | 
					    background-color: rgba(224, 247, 250, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.log-entry[data-type="out"] {
 | 
				
			||||||
 | 
					    background-color: rgba(255, 248, 225, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页容器 */
 | 
				
			||||||
 | 
					.pagination-container {
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    padding-left: 0;
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    gap: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination .page-item {
 | 
				
			||||||
 | 
					    margin: 0 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination .page-link {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    padding: 8px 16px;
 | 
				
			||||||
 | 
					    color: #4169e1;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    border: 1px solid #e1f0ff;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    min-width: 40px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination .page-link:hover {
 | 
				
			||||||
 | 
					    background-color: #e1f0ff;
 | 
				
			||||||
 | 
					    color: #2c3e50;
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 10px rgba(79, 149, 255, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination .page-item.active .page-link {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #7AB6FF, #5e81ac);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border-color: #5e81ac;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-pagination .page-item.disabled .page-link {
 | 
				
			||||||
 | 
					    color: #95a5a6;
 | 
				
			||||||
 | 
					    background-color: #f8f9fa;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 页脚 */
 | 
				
			||||||
 | 
					.card-footer-frozen {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #ecf5ff, #d8e6ff);
 | 
				
			||||||
 | 
					    padding: 1.5rem;
 | 
				
			||||||
 | 
					    border-radius: 0 0 18px 18px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer-actions {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer-decoration {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 15px;
 | 
				
			||||||
 | 
					    background-image: url('https://i.imgur.com/KkMfwWv.png');
 | 
				
			||||||
 | 
					    background-size: 50px;
 | 
				
			||||||
 | 
					    background-repeat: repeat-x;
 | 
				
			||||||
 | 
					    opacity: 0.2;
 | 
				
			||||||
 | 
					    filter: hue-rotate(190deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 冰雪风格按钮 */
 | 
				
			||||||
 | 
					.frozen-btn {
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-btn i {
 | 
				
			||||||
 | 
					    margin-right: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-btn {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #81a1c1, #5e81ac);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-btn:hover {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #5e81ac, #4c6f94);
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					    box-shadow: 0 8px 15px rgba(94, 129, 172, 0.3);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.adjust-btn {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #7AB6FF, #5d91e5);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.adjust-btn:hover {
 | 
				
			||||||
 | 
					    background: linear-gradient(45deg, #5d91e5, #4169e1);
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					    box-shadow: 0 8px 15px rgba(65, 105, 225, 0.3);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-btn::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -50%;
 | 
				
			||||||
 | 
					    left: -50%;
 | 
				
			||||||
 | 
					    width: 200%;
 | 
				
			||||||
 | 
					    height: 200%;
 | 
				
			||||||
 | 
					    background: rgba(255, 255, 255, 0.1);
 | 
				
			||||||
 | 
					    transform: rotate(45deg);
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.frozen-btn:hover::after {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    transform: rotate(45deg) translateY(-50%);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 动画类 */
 | 
				
			||||||
 | 
					.fade-in {
 | 
				
			||||||
 | 
					    animation: fadeIn 0.5s ease forwards;
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes fadeIn {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateY(20px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateY(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.selected-row {
 | 
				
			||||||
 | 
					    background-color: #e3f2fd !important;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.selected-row::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset: 0;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, rgba(122, 182, 255, 0.1), transparent);
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式调整 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .table-header-row,
 | 
				
			||||||
 | 
					    .table-row {
 | 
				
			||||||
 | 
					        grid-template-columns: 0.5fr 1fr 0.8fr 0.8fr 1fr 1.2fr 1.2fr;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-info {
 | 
				
			||||||
 | 
					        grid-template-columns: 1fr;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .book-cover-container {
 | 
				
			||||||
 | 
					        margin-bottom: 30px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-frame {
 | 
				
			||||||
 | 
					        transform: rotate(0);
 | 
				
			||||||
 | 
					        max-width: 180px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-cover {
 | 
				
			||||||
 | 
					        transform: rotate(0);
 | 
				
			||||||
 | 
					        max-height: 200px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-title {
 | 
				
			||||||
 | 
					        text-align: center;
 | 
				
			||||||
 | 
					        font-size: 1.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .table-header-row,
 | 
				
			||||||
 | 
					    .table-row {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .th-frozen:after {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .th-frozen {
 | 
				
			||||||
 | 
					        text-align: left;
 | 
				
			||||||
 | 
					        padding: 10px 15px;
 | 
				
			||||||
 | 
					        border-bottom: 1px solid rgba(255, 255, 255, 0.2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .td-frozen {
 | 
				
			||||||
 | 
					        justify-content: flex-start;
 | 
				
			||||||
 | 
					        padding: 10px 15px;
 | 
				
			||||||
 | 
					        border-bottom: 1px solid #ecf0f1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .td-frozen:before {
 | 
				
			||||||
 | 
					        content: attr(data-label);
 | 
				
			||||||
 | 
					        font-weight: 600;
 | 
				
			||||||
 | 
					        margin-right: 10px;
 | 
				
			||||||
 | 
					        color: #7f8c8d;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .footer-actions {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        gap: 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .frozen-btn {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										417
									
								
								app/static/css/inventory-list.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								app/static/css/inventory-list.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,417 @@
 | 
				
			|||||||
 | 
					/* 全局变量设置 */
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --primary-color: #f2a3b3;
 | 
				
			||||||
 | 
					    --primary-light: #ffd6e0;
 | 
				
			||||||
 | 
					    --primary-dark: #e57f9a;
 | 
				
			||||||
 | 
					    --secondary-color: #a9d1f7;
 | 
				
			||||||
 | 
					    --text-color: #4a4a4a;
 | 
				
			||||||
 | 
					    --light-text: #6e6e6e;
 | 
				
			||||||
 | 
					    --success-color: #77dd77;
 | 
				
			||||||
 | 
					    --warning-color: #fdfd96;
 | 
				
			||||||
 | 
					    --danger-color: #ff9e9e;
 | 
				
			||||||
 | 
					    --background-color: #fff9fb;
 | 
				
			||||||
 | 
					    --card-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					    --transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    --border-radius: 12px;
 | 
				
			||||||
 | 
					    --card-padding: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 基础样式 */
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    background-color: var(--background-color);
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    font-family: 'Helvetica Neue', Arial, sans-serif;
 | 
				
			||||||
 | 
					    line-height: 1.6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-container {
 | 
				
			||||||
 | 
					    max-width: 1200px;
 | 
				
			||||||
 | 
					    margin: 0 auto;
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 页面标题 */
 | 
				
			||||||
 | 
					.page-header {
 | 
				
			||||||
 | 
					    background: linear-gradient(135deg, var(--primary-light), var(--secondary-color));
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    margin-bottom: 30px;
 | 
				
			||||||
 | 
					    padding: 40px 30px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    box-shadow: var(--card-shadow);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					    background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M95,50A45,45,0,1,1,50,5,45,45,0,0,1,95,50Z" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="10"/></svg>') repeat;
 | 
				
			||||||
 | 
					    background-size: 80px 80px;
 | 
				
			||||||
 | 
					    opacity: 0.4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.header-content {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header h1 {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    font-size: 2.5rem;
 | 
				
			||||||
 | 
					    font-weight: 300;
 | 
				
			||||||
 | 
					    letter-spacing: 1px;
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.header-icon {
 | 
				
			||||||
 | 
					    margin-right: 15px;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.subtitle {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    font-size: 1.1rem;
 | 
				
			||||||
 | 
					    font-weight: 300;
 | 
				
			||||||
 | 
					    opacity: 0.9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 搜索框样式 */
 | 
				
			||||||
 | 
					.search-card {
 | 
				
			||||||
 | 
					    background: #fff;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    padding: var(--card-padding);
 | 
				
			||||||
 | 
					    margin-bottom: 30px;
 | 
				
			||||||
 | 
					    box-shadow: var(--card-shadow);
 | 
				
			||||||
 | 
					    border-top: 4px solid var(--primary-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-form {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 15px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-input-group {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    min-width: 300px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-input-container {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-icon {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 15px;
 | 
				
			||||||
 | 
					    top: 50%;
 | 
				
			||||||
 | 
					    transform: translateY(-50%);
 | 
				
			||||||
 | 
					    color: var(--light-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-input {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 12px 15px 12px 40px;
 | 
				
			||||||
 | 
					    border: 1px solid #e3e3e3;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius) 0 0 var(--border-radius);
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					    outline: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-input:focus {
 | 
				
			||||||
 | 
					    border-color: var(--primary-color);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 3px var(--primary-light);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-button {
 | 
				
			||||||
 | 
					    background-color: var(--primary-color);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    padding: 12px 25px;
 | 
				
			||||||
 | 
					    font-size: 1rem;
 | 
				
			||||||
 | 
					    border-radius: 0 var(--border-radius) var(--border-radius) 0;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.search-button:hover {
 | 
				
			||||||
 | 
					    background-color: var(--primary-dark);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.log-button {
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    color: var(--primary-color);
 | 
				
			||||||
 | 
					    border: 1px solid var(--primary-color);
 | 
				
			||||||
 | 
					    padding: 11px 20px;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    gap: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.log-button:hover {
 | 
				
			||||||
 | 
					    background-color: var(--primary-light);
 | 
				
			||||||
 | 
					    color: var(--primary-dark);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表格样式 */
 | 
				
			||||||
 | 
					.table-container {
 | 
				
			||||||
 | 
					    background: #fff;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    padding: var(--card-padding);
 | 
				
			||||||
 | 
					    margin-bottom: 30px;
 | 
				
			||||||
 | 
					    box-shadow: var(--card-shadow);
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					    font-size: 0.95rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table th {
 | 
				
			||||||
 | 
					    background-color: var(--primary-light);
 | 
				
			||||||
 | 
					    color: var(--primary-dark);
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    text-transform: uppercase;
 | 
				
			||||||
 | 
					    font-size: 0.85rem;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table tr {
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #f3f3f3;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table tr:last-child {
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table tr:hover {
 | 
				
			||||||
 | 
					    background-color: #f9f9f9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.inventory-table td {
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author {
 | 
				
			||||||
 | 
					    color: var(--light-text);
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 库存和状态标签样式 */
 | 
				
			||||||
 | 
					.stock-badge, .status-badge {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    padding: 6px 12px;
 | 
				
			||||||
 | 
					    border-radius: 50px;
 | 
				
			||||||
 | 
					    font-size: 0.85rem;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    min-width: 60px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-high {
 | 
				
			||||||
 | 
					    background-color: var(--success-color);
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-medium {
 | 
				
			||||||
 | 
					    background-color: var(--warning-color);
 | 
				
			||||||
 | 
					    color: #8a7800;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-low {
 | 
				
			||||||
 | 
					    background-color: var(--danger-color);
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.status-active {
 | 
				
			||||||
 | 
					    background-color: #d9f5e6;
 | 
				
			||||||
 | 
					    color: #2a9d5c;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.status-inactive {
 | 
				
			||||||
 | 
					    background-color: #ffe8e8;
 | 
				
			||||||
 | 
					    color: #e35555;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 操作按钮 */
 | 
				
			||||||
 | 
					.action-buttons {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-adjust, .btn-view {
 | 
				
			||||||
 | 
					    padding: 8px 12px;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-size: 0.85rem;
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    gap: 5px;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-adjust {
 | 
				
			||||||
 | 
					    background-color: var(--primary-light);
 | 
				
			||||||
 | 
					    color: var(--primary-dark);
 | 
				
			||||||
 | 
					    border: 1px solid var(--primary-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-adjust:hover {
 | 
				
			||||||
 | 
					    background-color: var(--primary-color);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-view {
 | 
				
			||||||
 | 
					    background-color: var(--secondary-color);
 | 
				
			||||||
 | 
					    color: #3573b5;
 | 
				
			||||||
 | 
					    border: 1px solid #8ab9e3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-view:hover {
 | 
				
			||||||
 | 
					    background-color: #8ab9e3;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页样式 */
 | 
				
			||||||
 | 
					.pagination-wrapper {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    gap: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-link {
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    min-width: 40px;
 | 
				
			||||||
 | 
					    height: 40px;
 | 
				
			||||||
 | 
					    padding: 0 15px;
 | 
				
			||||||
 | 
					    border-radius: var(--border-radius);
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    transition: var(--transition);
 | 
				
			||||||
 | 
					    border: 1px solid #e3e3e3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.active .page-link {
 | 
				
			||||||
 | 
					    background-color: var(--primary-color);
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border-color: var(--primary-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item:not(.active) .page-link:hover {
 | 
				
			||||||
 | 
					    background-color: var(--primary-light);
 | 
				
			||||||
 | 
					    color: var(--primary-dark);
 | 
				
			||||||
 | 
					    border-color: var(--primary-light);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.disabled .page-link {
 | 
				
			||||||
 | 
					    background-color: #f5f5f5;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式调整 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .inventory-container {
 | 
				
			||||||
 | 
					        padding: 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header {
 | 
				
			||||||
 | 
					        padding: 30px 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header h1 {
 | 
				
			||||||
 | 
					        font-size: 2rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .search-form {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        align-items: stretch;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .log-button {
 | 
				
			||||||
 | 
					        text-align: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header h1 {
 | 
				
			||||||
 | 
					        font-size: 1.8rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .table-container {
 | 
				
			||||||
 | 
					        overflow-x: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .inventory-table {
 | 
				
			||||||
 | 
					        min-width: 800px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .action-buttons {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .btn-adjust, .btn-view {
 | 
				
			||||||
 | 
					        text-align: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 576px) {
 | 
				
			||||||
 | 
					    .page-header {
 | 
				
			||||||
 | 
					        padding: 25px 15px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header h1 {
 | 
				
			||||||
 | 
					        font-size: 1.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .subtitle {
 | 
				
			||||||
 | 
					        font-size: 1rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .pagination .page-link {
 | 
				
			||||||
 | 
					        min-width: 35px;
 | 
				
			||||||
 | 
					        height: 35px;
 | 
				
			||||||
 | 
					        padding: 0 10px;
 | 
				
			||||||
 | 
					        font-size: 0.9rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										710
									
								
								app/static/css/inventory-logs.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										710
									
								
								app/static/css/inventory-logs.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,710 @@
 | 
				
			|||||||
 | 
					/* 冰雪奇缘主题库存日志页面样式 */
 | 
				
			||||||
 | 
					@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Nunito:wght@300;400;600;700&display=swap');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					  --primary-blue: #6fa8dc;
 | 
				
			||||||
 | 
					  --light-blue: #cfe2f3;
 | 
				
			||||||
 | 
					  --dark-blue: #1a5190;
 | 
				
			||||||
 | 
					  --accent-pink: #f4b8c4;
 | 
				
			||||||
 | 
					  --accent-purple: #b19cd9;
 | 
				
			||||||
 | 
					  --subtle-gold: #ffd966;
 | 
				
			||||||
 | 
					  --ice-white: #f3f9ff;
 | 
				
			||||||
 | 
					  --snow-white: #ffffff;
 | 
				
			||||||
 | 
					  --text-dark: #2c3e50;
 | 
				
			||||||
 | 
					  --text-light: #ecf0f1;
 | 
				
			||||||
 | 
					  --shadow-color: rgba(0, 53, 102, 0.15);
 | 
				
			||||||
 | 
					  --frost-blue: #a2d5f2;
 | 
				
			||||||
 | 
					  --elsa-blue: #85c1e9;
 | 
				
			||||||
 | 
					  --anna-purple: #c39bd3;
 | 
				
			||||||
 | 
					  --olaf-white: #f9fcff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 全局样式重置 */
 | 
				
			||||||
 | 
					* {
 | 
				
			||||||
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					  font-family: 'Nunito', sans-serif;
 | 
				
			||||||
 | 
					  background: #f5f9ff url('/static/images/disney-bg.jpg') no-repeat center center fixed;
 | 
				
			||||||
 | 
					  background-size: cover;
 | 
				
			||||||
 | 
					  color: var(--text-dark);
 | 
				
			||||||
 | 
					  line-height: 1.6;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow-x: hidden;
 | 
				
			||||||
 | 
					  min-height: 100vh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 容器样式 */
 | 
				
			||||||
 | 
					.disney-container {
 | 
				
			||||||
 | 
					  max-width: 95%;
 | 
				
			||||||
 | 
					  margin: 2rem auto;
 | 
				
			||||||
 | 
					  padding: 0 15px;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 魔法粒子效果层 */
 | 
				
			||||||
 | 
					#magic-particles {
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  z-index: 0;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 主卡片样式 */
 | 
				
			||||||
 | 
					.disney-card {
 | 
				
			||||||
 | 
					  background: linear-gradient(135deg, var(--ice-white) 0%, var(--snow-white) 100%);
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  box-shadow: 0 10px 30px var(--shadow-color),
 | 
				
			||||||
 | 
					              0 0 50px rgba(137, 196, 244, 0.3),
 | 
				
			||||||
 | 
					              inset 0 0 15px rgba(255, 255, 255, 0.8);
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  margin-bottom: 2rem;
 | 
				
			||||||
 | 
					  border: 1px solid rgba(200, 223, 255, 0.8);
 | 
				
			||||||
 | 
					  animation: card-glow 3s infinite alternate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片发光动画 */
 | 
				
			||||||
 | 
					@keyframes card-glow {
 | 
				
			||||||
 | 
					  from {
 | 
				
			||||||
 | 
					    box-shadow: 0 10px 30px var(--shadow-color),
 | 
				
			||||||
 | 
					                0 0 50px rgba(137, 196, 244, 0.3),
 | 
				
			||||||
 | 
					                inset 0 0 15px rgba(255, 255, 255, 0.8);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  to {
 | 
				
			||||||
 | 
					    box-shadow: 0 10px 30px var(--shadow-color),
 | 
				
			||||||
 | 
					                0 0 70px rgba(137, 196, 244, 0.5),
 | 
				
			||||||
 | 
					                inset 0 0 20px rgba(255, 255, 255, 0.9);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 装饰元素 */
 | 
				
			||||||
 | 
					.disney-decoration {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  background-size: contain;
 | 
				
			||||||
 | 
					  background-repeat: no-repeat;
 | 
				
			||||||
 | 
					  opacity: 0.7;
 | 
				
			||||||
 | 
					  z-index: 1;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-icon {
 | 
				
			||||||
 | 
					  top: 20px;
 | 
				
			||||||
 | 
					  right: 30px;
 | 
				
			||||||
 | 
					  width: 60px;
 | 
				
			||||||
 | 
					  height: 60px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/ph:books-duotone.svg?color=%236fa8dc');
 | 
				
			||||||
 | 
					  transform: rotate(10deg);
 | 
				
			||||||
 | 
					  animation: float 6s ease-in-out infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.crown-icon {
 | 
				
			||||||
 | 
					  bottom: 40px;
 | 
				
			||||||
 | 
					  left: 20px;
 | 
				
			||||||
 | 
					  width: 50px;
 | 
				
			||||||
 | 
					  height: 50px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa6-solid:crown.svg?color=%23ffd966');
 | 
				
			||||||
 | 
					  animation: float 5s ease-in-out infinite 1s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.wand-icon {
 | 
				
			||||||
 | 
					  top: 60px;
 | 
				
			||||||
 | 
					  left: 40px;
 | 
				
			||||||
 | 
					  width: 40px;
 | 
				
			||||||
 | 
					  height: 40px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-solid:magic.svg?color=%23b19cd9');
 | 
				
			||||||
 | 
					  animation: float 7s ease-in-out infinite 0.5s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.snowflake-icon {
 | 
				
			||||||
 | 
					  bottom: 70px;
 | 
				
			||||||
 | 
					  right: 50px;
 | 
				
			||||||
 | 
					  width: 45px;
 | 
				
			||||||
 | 
					  height: 45px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-regular:snowflake.svg?color=%23a2d5f2');
 | 
				
			||||||
 | 
					  animation: float 4s ease-in-out infinite 1.5s, spin 15s linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes float {
 | 
				
			||||||
 | 
					  0% { transform: translateY(0) rotate(0deg); }
 | 
				
			||||||
 | 
					  50% { transform: translateY(-15px) rotate(5deg); }
 | 
				
			||||||
 | 
					  100% { transform: translateY(0) rotate(0deg); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes spin {
 | 
				
			||||||
 | 
					  from { transform: rotate(0deg); }
 | 
				
			||||||
 | 
					  to { transform: rotate(360deg); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片头部 */
 | 
				
			||||||
 | 
					.card-header-disney {
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, var(--elsa-blue), var(--frost-blue));
 | 
				
			||||||
 | 
					  color: var(--text-light);
 | 
				
			||||||
 | 
					  padding: 1.5rem;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  border-bottom: 3px solid rgba(255, 255, 255, 0.5);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-header-disney h4 {
 | 
				
			||||||
 | 
					  font-size: 1.8rem;
 | 
				
			||||||
 | 
					  font-weight: 700;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  z-index: 2;
 | 
				
			||||||
 | 
					  font-family: 'Dancing Script', cursive;
 | 
				
			||||||
 | 
					  letter-spacing: 1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card-header-disney i {
 | 
				
			||||||
 | 
					  margin-right: 10px;
 | 
				
			||||||
 | 
					  color: var(--subtle-gold);
 | 
				
			||||||
 | 
					  animation: pulse 2s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.princess-crown {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: -20px;
 | 
				
			||||||
 | 
					  left: 50%;
 | 
				
			||||||
 | 
					  transform: translateX(-50%);
 | 
				
			||||||
 | 
					  width: 60px;
 | 
				
			||||||
 | 
					  height: 30px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa6-solid:crown.svg?color=%23ffd966');
 | 
				
			||||||
 | 
					  background-size: contain;
 | 
				
			||||||
 | 
					  background-repeat: no-repeat;
 | 
				
			||||||
 | 
					  background-position: center;
 | 
				
			||||||
 | 
					  filter: drop-shadow(0 0 5px rgba(255, 217, 102, 0.7));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes pulse {
 | 
				
			||||||
 | 
					  0% { transform: scale(1); }
 | 
				
			||||||
 | 
					  50% { transform: scale(1.1); }
 | 
				
			||||||
 | 
					  100% { transform: scale(1); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片内容 */
 | 
				
			||||||
 | 
					.card-body-disney {
 | 
				
			||||||
 | 
					  padding: 2rem;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 图书信息部分 */
 | 
				
			||||||
 | 
					.book-details-container {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  background: linear-gradient(to right, rgba(162, 213, 242, 0.1), rgba(177, 156, 217, 0.1));
 | 
				
			||||||
 | 
					  border-radius: 15px;
 | 
				
			||||||
 | 
					  padding: 1.5rem;
 | 
				
			||||||
 | 
					  margin-bottom: 2rem;
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					  border: 1px solid rgba(162, 213, 242, 0.3);
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover-wrapper {
 | 
				
			||||||
 | 
					  flex: 0 0 150px;
 | 
				
			||||||
 | 
					  margin-right: 2rem;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-book-cover {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: auto;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  transition: transform 0.3s ease, box-shadow 0.3s ease;
 | 
				
			||||||
 | 
					  border: 4px solid white;
 | 
				
			||||||
 | 
					  object-fit: cover;
 | 
				
			||||||
 | 
					  z-index: 2;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-book-cover:hover {
 | 
				
			||||||
 | 
					  transform: translateY(-5px) scale(1.03);
 | 
				
			||||||
 | 
					  box-shadow: 0 15px 25px rgba(0, 0, 0, 0.15);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover-glow {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  background: radial-gradient(circle, rgba(162, 213, 242, 0.6) 0%, rgba(255, 255, 255, 0) 70%);
 | 
				
			||||||
 | 
					  z-index: 1;
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  transition: opacity 0.3s ease;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover-wrapper:hover .book-cover-glow {
 | 
				
			||||||
 | 
					  opacity: 1;
 | 
				
			||||||
 | 
					  animation: glow-pulse 2s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes glow-pulse {
 | 
				
			||||||
 | 
					  0% { opacity: 0.3; }
 | 
				
			||||||
 | 
					  50% { opacity: 0.7; }
 | 
				
			||||||
 | 
					  100% { opacity: 0.3; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-info {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					  font-family: 'Dancing Script', cursive;
 | 
				
			||||||
 | 
					  font-size: 2rem;
 | 
				
			||||||
 | 
					  margin-bottom: 1rem;
 | 
				
			||||||
 | 
					  color: var(--dark-blue);
 | 
				
			||||||
 | 
					  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title::after {
 | 
				
			||||||
 | 
					  content: '';
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  bottom: -5px;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 2px;
 | 
				
			||||||
 | 
					  background: linear-gradient(to right, var(--elsa-blue), var(--anna-purple));
 | 
				
			||||||
 | 
					  border-radius: 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.info-row {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  margin-bottom: 0.8rem;
 | 
				
			||||||
 | 
					  font-size: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-icon {
 | 
				
			||||||
 | 
					  width: 24px;
 | 
				
			||||||
 | 
					  height: 24px;
 | 
				
			||||||
 | 
					  margin-right: 10px;
 | 
				
			||||||
 | 
					  background-size: contain;
 | 
				
			||||||
 | 
					  background-repeat: no-repeat;
 | 
				
			||||||
 | 
					  background-position: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.author-icon {
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-solid:user-edit.svg?color=%236fa8dc');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.publisher-icon {
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-solid:building.svg?color=%236fa8dc');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.isbn-icon {
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-solid:barcode.svg?color=%236fa8dc');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-icon {
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/fa-solid:warehouse.svg?color=%236fa8dc');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.stock-badge {
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  padding: 3px 10px;
 | 
				
			||||||
 | 
					  border-radius: 12px;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					  font-size: 0.9rem;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  margin-left: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.high-stock {
 | 
				
			||||||
 | 
					  background-color: #2ecc71;
 | 
				
			||||||
 | 
					  animation: badge-pulse 2s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.low-stock {
 | 
				
			||||||
 | 
					  background-color: #f39c12;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.out-stock {
 | 
				
			||||||
 | 
					  background-color: #e74c3c;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes badge-pulse {
 | 
				
			||||||
 | 
					  0% { transform: scale(1); }
 | 
				
			||||||
 | 
					  50% { transform: scale(1.1); }
 | 
				
			||||||
 | 
					  100% { transform: scale(1); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 日志部分 */
 | 
				
			||||||
 | 
					.logs-section {
 | 
				
			||||||
 | 
					  background-color: var(--ice-white);
 | 
				
			||||||
 | 
					  border-radius: 15px;
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					  padding: 1.5rem;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  border: 1px solid rgba(162, 213, 242, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.logs-section::before {
 | 
				
			||||||
 | 
					  content: '';
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/ph:snowflake-thin.svg?color=%23a2d5f2');
 | 
				
			||||||
 | 
					  background-size: 20px;
 | 
				
			||||||
 | 
					  opacity: 0.05;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  animation: snow-bg 60s linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes snow-bg {
 | 
				
			||||||
 | 
					  from { background-position: 0 0; }
 | 
				
			||||||
 | 
					  to { background-position: 100% 100%; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.logs-title {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  font-size: 1.5rem;
 | 
				
			||||||
 | 
					  margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					  color: var(--dark-blue);
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  font-family: 'Dancing Script', cursive;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title-decoration {
 | 
				
			||||||
 | 
					  width: 100px;
 | 
				
			||||||
 | 
					  height: 2px;
 | 
				
			||||||
 | 
					  background: linear-gradient(to right, transparent, var(--elsa-blue), transparent);
 | 
				
			||||||
 | 
					  margin: 0 15px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title-decoration.right {
 | 
				
			||||||
 | 
					  transform: scaleX(-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.table-container {
 | 
				
			||||||
 | 
					  overflow-x: auto;
 | 
				
			||||||
 | 
					  border-radius: 10px;
 | 
				
			||||||
 | 
					  box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					  margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  border-collapse: separate;
 | 
				
			||||||
 | 
					  border-spacing: 0;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  border-radius: 10px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table thead {
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, var(--elsa-blue), var(--frost-blue));
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table th {
 | 
				
			||||||
 | 
					  padding: 1rem 0.8rem;
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table th:first-child {
 | 
				
			||||||
 | 
					  border-top-left-radius: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table th:last-child {
 | 
				
			||||||
 | 
					  border-top-right-radius: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-table td {
 | 
				
			||||||
 | 
					  padding: 0.8rem;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid rgba(162, 213, 242, 0.2);
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.log-row {
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.log-row:hover {
 | 
				
			||||||
 | 
					  background-color: rgba(162, 213, 242, 0.1);
 | 
				
			||||||
 | 
					  transform: translateY(-2px);
 | 
				
			||||||
 | 
					  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.operation-badge {
 | 
				
			||||||
 | 
					  padding: 4px 10px;
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  font-size: 0.8rem;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  min-width: 80px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.in-badge {
 | 
				
			||||||
 | 
					  background-color: rgba(46, 204, 113, 0.15);
 | 
				
			||||||
 | 
					  color: #27ae60;
 | 
				
			||||||
 | 
					  border: 1px solid rgba(46, 204, 113, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.out-badge {
 | 
				
			||||||
 | 
					  background-color: rgba(231, 76, 60, 0.15);
 | 
				
			||||||
 | 
					  color: #c0392b;
 | 
				
			||||||
 | 
					  border: 1px solid rgba(231, 76, 60, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.remark-cell {
 | 
				
			||||||
 | 
					  max-width: 200px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  text-overflow: ellipsis;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-logs {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  padding: 3rem 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-state {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  color: #95a5a6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-icon {
 | 
				
			||||||
 | 
					  width: 80px;
 | 
				
			||||||
 | 
					  height: 80px;
 | 
				
			||||||
 | 
					  background-image: url('https://api.iconify.design/ph:book-open-duotone.svg?color=%2395a5a6');
 | 
				
			||||||
 | 
					  background-size: contain;
 | 
				
			||||||
 | 
					  background-repeat: no-repeat;
 | 
				
			||||||
 | 
					  background-position: center;
 | 
				
			||||||
 | 
					  margin-bottom: 1rem;
 | 
				
			||||||
 | 
					  opacity: 0.7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-state p {
 | 
				
			||||||
 | 
					  font-size: 1.1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页样式 */
 | 
				
			||||||
 | 
					.disney-pagination {
 | 
				
			||||||
 | 
					  margin-top: 1.5rem;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination-list {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  gap: 5px;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item {
 | 
				
			||||||
 | 
					  margin: 0 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-link {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  padding: 8px 12px;
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  color: var(--dark-blue);
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					  min-width: 40px;
 | 
				
			||||||
 | 
					  border: 1px solid rgba(111, 168, 220, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-link:hover:not(.disabled .page-link) {
 | 
				
			||||||
 | 
					  background-color: var(--light-blue);
 | 
				
			||||||
 | 
					  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  transform: translateY(-2px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.active .page-link {
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, var(--elsa-blue), var(--frost-blue));
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  box-shadow: 0 4px 8px rgba(111, 168, 220, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.dots .page-link {
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  background: none;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.disabled .page-link {
 | 
				
			||||||
 | 
					  color: #b2bec3;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  background-color: rgba(236, 240, 241, 0.5);
 | 
				
			||||||
 | 
					  border: 1px solid rgba(189, 195, 199, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 卡片底部 */
 | 
				
			||||||
 | 
					.card-footer-disney {
 | 
				
			||||||
 | 
					  padding: 1.5rem;
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, rgba(162, 213, 242, 0.2), rgba(177, 156, 217, 0.2));
 | 
				
			||||||
 | 
					  border-top: 1px solid rgba(162, 213, 242, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.button-container {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  gap: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-button {
 | 
				
			||||||
 | 
					  padding: 10px 20px;
 | 
				
			||||||
 | 
					  border-radius: 30px;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  z-index: 1;
 | 
				
			||||||
 | 
					  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-button::before {
 | 
				
			||||||
 | 
					  content: '';
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: -100%;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
 | 
				
			||||||
 | 
					  transition: all 0.6s ease;
 | 
				
			||||||
 | 
					  z-index: -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-button:hover::before {
 | 
				
			||||||
 | 
					  left: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disney-button:hover {
 | 
				
			||||||
 | 
					  transform: translateY(-3px);
 | 
				
			||||||
 | 
					  box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.button-icon {
 | 
				
			||||||
 | 
					  margin-right: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-btn {
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, #3498db, #2980b9);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.adjust-btn {
 | 
				
			||||||
 | 
					  background: linear-gradient(45deg, #9b59b6, #8e44ad);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式设计 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					  .book-details-container {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .book-cover-wrapper {
 | 
				
			||||||
 | 
					    margin-right: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 1.5rem;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    width: 180px;
 | 
				
			||||||
 | 
					    margin: 0 auto 1.5rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .logs-title {
 | 
				
			||||||
 | 
					    font-size: 1.3rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .title-decoration {
 | 
				
			||||||
 | 
					    width: 50px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					  .disney-container {
 | 
				
			||||||
 | 
					    margin: 1rem auto;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .card-header-disney h4 {
 | 
				
			||||||
 | 
					    font-size: 1.5rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .card-body-disney {
 | 
				
			||||||
 | 
					    padding: 1.5rem 1rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .book-title {
 | 
				
			||||||
 | 
					    font-size: 1.7rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .disney-button {
 | 
				
			||||||
 | 
					    padding: 8px 15px;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 576px) {
 | 
				
			||||||
 | 
					  .button-container {
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    gap: 1rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .book-title {
 | 
				
			||||||
 | 
					    font-size: 1.5rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .logs-title {
 | 
				
			||||||
 | 
					    font-size: 1.2rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .title-decoration {
 | 
				
			||||||
 | 
					    width: 30px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 飘落的雪花 */
 | 
				
			||||||
 | 
					.snowflake {
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  top: -50px;
 | 
				
			||||||
 | 
					  animation: fall linear infinite;
 | 
				
			||||||
 | 
					  z-index: 0;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  color: rgba(255, 255, 255, 0.8);
 | 
				
			||||||
 | 
					  text-shadow: 0 0 5px rgba(162, 213, 242, 0.5);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes fall {
 | 
				
			||||||
 | 
					  to {
 | 
				
			||||||
 | 
					    transform: translateY(100vh) rotate(360deg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										474
									
								
								app/static/css/my_borrows.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								app/static/css/my_borrows.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,474 @@
 | 
				
			|||||||
 | 
					/* my_borrows.css - 少女粉色风格图书管理系统 */
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					  --primary-color: #e686a5;  /* 主要粉色 */
 | 
				
			||||||
 | 
					  --primary-light: #ffedf2;  /* 浅粉色 */
 | 
				
			||||||
 | 
					  --primary-dark: #d26a8c;   /* 深粉色 */
 | 
				
			||||||
 | 
					  --accent-color: #9a83c9;   /* 紫色点缀 */
 | 
				
			||||||
 | 
					  --text-primary: #4a4a4a;   /* 主要文字颜色 */
 | 
				
			||||||
 | 
					  --text-secondary: #848484; /* 次要文字颜色 */
 | 
				
			||||||
 | 
					  --border-color: #f4d7e1;   /* 边框颜色 */
 | 
				
			||||||
 | 
					  --success-color: #7ac9a1;  /* 成功色 */
 | 
				
			||||||
 | 
					  --danger-color: #ff8f9e;   /* 危险色 */
 | 
				
			||||||
 | 
					  --white: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
 | 
				
			||||||
 | 
					  color: var(--text-primary);
 | 
				
			||||||
 | 
					  background-color: #fdf6f8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 容器 */
 | 
				
			||||||
 | 
					.container {
 | 
				
			||||||
 | 
					  width: 95% !important;
 | 
				
			||||||
 | 
					  max-width: 1200px !important;
 | 
				
			||||||
 | 
					  margin: 1.5rem auto;
 | 
				
			||||||
 | 
					  padding: 1.5rem;
 | 
				
			||||||
 | 
					  background-color: var(--white);
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 15px rgba(230, 134, 165, 0.15);
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 页面标题 */
 | 
				
			||||||
 | 
					.page-title {
 | 
				
			||||||
 | 
					  margin-bottom: 1.8rem;
 | 
				
			||||||
 | 
					  color: var(--primary-dark);
 | 
				
			||||||
 | 
					  border-bottom: 2px solid var(--border-color);
 | 
				
			||||||
 | 
					  padding-bottom: 12px;
 | 
				
			||||||
 | 
					  font-size: 1.8rem;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-title:after {
 | 
				
			||||||
 | 
					  content: "";
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  width: 80px;
 | 
				
			||||||
 | 
					  height: 3px;
 | 
				
			||||||
 | 
					  background-color: var(--primary-color);
 | 
				
			||||||
 | 
					  bottom: -2px;
 | 
				
			||||||
 | 
					  left: 50%;
 | 
				
			||||||
 | 
					  transform: translateX(-50%);
 | 
				
			||||||
 | 
					  border-radius: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 标签页样式 */
 | 
				
			||||||
 | 
					.tabs {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  margin-bottom: 25px;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  background-color: var(--primary-light);
 | 
				
			||||||
 | 
					  border-radius: 25px;
 | 
				
			||||||
 | 
					  padding: 5px;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 10px rgba(230, 134, 165, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* tab 项 */
 | 
				
			||||||
 | 
					.tab {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  padding: 10px 20px;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  color: var(--text-primary);
 | 
				
			||||||
 | 
					  margin-right: 2px;
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					  font-size: 0.95rem;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab:hover {
 | 
				
			||||||
 | 
					  background-color: rgba(230, 134, 165, 0.1);
 | 
				
			||||||
 | 
					  color: var(--primary-dark);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.active {
 | 
				
			||||||
 | 
					  background-color: var(--primary-color);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 8px rgba(230, 134, 165, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.count {
 | 
				
			||||||
 | 
					  background-color: rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  padding: 2px 8px;
 | 
				
			||||||
 | 
					  font-size: 0.75em;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  margin-left: 5px;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tab.active .count {
 | 
				
			||||||
 | 
					  background-color: rgba(255, 255, 255, 0.4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 借阅列表与表格 */
 | 
				
			||||||
 | 
					.borrow-list {
 | 
				
			||||||
 | 
					  margin-top: 20px;
 | 
				
			||||||
 | 
					  margin-bottom: 2rem;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  overflow-x: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  border-collapse: separate;
 | 
				
			||||||
 | 
					  border-spacing: 0;
 | 
				
			||||||
 | 
					  margin-bottom: 25px;
 | 
				
			||||||
 | 
					  border-radius: 15px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 20px rgba(230, 134, 165, 0.08);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 调整列宽 - 解决状态列和操作列问题 */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(1),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(1) { width: 90px; }
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(2),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(2) { width: 20%; }
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(3),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(3),
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(4),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(4) { width: 15%; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 状态列 */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(5),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(5) {
 | 
				
			||||||
 | 
					  width: 15%;
 | 
				
			||||||
 | 
					  min-width: 120px;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  overflow: visible;
 | 
				
			||||||
 | 
					  padding: 14px 25px;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 状态表头文字微调 - 向右移动2px */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(5) {
 | 
				
			||||||
 | 
					  padding-left: 28px; /* 增加左内边距,使文字看起来稍微向右移动 */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 操作列 */
 | 
				
			||||||
 | 
					.borrow-table th:nth-child(6),
 | 
				
			||||||
 | 
					.borrow-table td:nth-child(6) {
 | 
				
			||||||
 | 
					  width: 18%;
 | 
				
			||||||
 | 
					  min-width: 140px;
 | 
				
			||||||
 | 
					  padding: 14px 18px;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  padding: 14px 0 14px 15px; /* 减少右内边距,增加左内边距 */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table th,
 | 
				
			||||||
 | 
					.borrow-table td {
 | 
				
			||||||
 | 
					  padding: 14px 18px;
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table th {
 | 
				
			||||||
 | 
					  background-color: var(--primary-light);
 | 
				
			||||||
 | 
					  color: var(--primary-dark);
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  font-size: 0.9rem;
 | 
				
			||||||
 | 
					  letter-spacing: 0.3px;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table tr {
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					  transition: all 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table tbody tr:last-child {
 | 
				
			||||||
 | 
					  border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-item {
 | 
				
			||||||
 | 
					  background-color: var(--white);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-item:hover {
 | 
				
			||||||
 | 
					  background-color: rgba(230, 134, 165, 0.03);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-item.overdue {
 | 
				
			||||||
 | 
					  background-color: rgba(255, 143, 158, 0.08);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 图书封面 */
 | 
				
			||||||
 | 
					.book-cover img {
 | 
				
			||||||
 | 
					  width: 65px;
 | 
				
			||||||
 | 
					  height: 90px;
 | 
				
			||||||
 | 
					  object-fit: cover;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					  border: 3px solid var(--white);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img:hover {
 | 
				
			||||||
 | 
					  transform: scale(1.05);
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 15px rgba(230, 134, 165, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 书名与作者 */
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  font-size: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a {
 | 
				
			||||||
 | 
					  color: var(--primary-dark);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  transition: color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a:hover {
 | 
				
			||||||
 | 
					  color: var(--primary-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author {
 | 
				
			||||||
 | 
					  color: var(--text-secondary);
 | 
				
			||||||
 | 
					  font-size: 0.85rem;
 | 
				
			||||||
 | 
					  margin-top: 5px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author:before {
 | 
				
			||||||
 | 
					  content: "🖋";
 | 
				
			||||||
 | 
					  margin-right: 5px;
 | 
				
			||||||
 | 
					  font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 徽章 - 修复状态显示问题 */
 | 
				
			||||||
 | 
					.borrow-table .badge,
 | 
				
			||||||
 | 
					.book-status .badge {
 | 
				
			||||||
 | 
					  padding: 4px 10px;
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  font-size: 0.75rem;
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  margin-bottom: 4px;
 | 
				
			||||||
 | 
					  letter-spacing: 0.3px;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  min-width: 60px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.borrow-table .badge {
 | 
				
			||||||
 | 
					  position: static;
 | 
				
			||||||
 | 
					  top: auto;
 | 
				
			||||||
 | 
					  right: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-primary { background-color: var(--primary-color); color: white; }
 | 
				
			||||||
 | 
					.badge-success { background-color: var(--success-color); color: white; }
 | 
				
			||||||
 | 
					.badge-danger  { background-color: var(--danger-color); color: white; }
 | 
				
			||||||
 | 
					.badge-info    { background-color: var(--accent-color); color: white; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-date {
 | 
				
			||||||
 | 
					  color: var(--text-secondary);
 | 
				
			||||||
 | 
					  font-size: 0.85rem;
 | 
				
			||||||
 | 
					  margin-top: 5px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-date:before {
 | 
				
			||||||
 | 
					  content: "📅";
 | 
				
			||||||
 | 
					  margin-right: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-danger {
 | 
				
			||||||
 | 
					  color: var(--danger-color) !important;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 操作按钮 - 简化样式 */
 | 
				
			||||||
 | 
					.actions {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: row;
 | 
				
			||||||
 | 
					  gap: 10px;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding-left: 15px; /* 整体左移5px */
 | 
				
			||||||
 | 
					  margin-top: 17px;
 | 
				
			||||||
 | 
					  margin-right: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actions .btn {
 | 
				
			||||||
 | 
					  min-width: 60px;
 | 
				
			||||||
 | 
					  padding: 8px 15px;
 | 
				
			||||||
 | 
					  font-size: 0.85rem;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  border-radius: 20px;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success {
 | 
				
			||||||
 | 
					  background-color: var(--success-color);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success:hover {
 | 
				
			||||||
 | 
					  background-color: #65b088;
 | 
				
			||||||
 | 
					  transform: translateY(-2px);
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 12px rgba(122, 201, 161, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					  background-color: var(--primary-color);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover {
 | 
				
			||||||
 | 
					  background-color: var(--primary-dark);
 | 
				
			||||||
 | 
					  transform: translateY(-2px);
 | 
				
			||||||
 | 
					  box-shadow: 0 5px 12px rgba(230, 134, 165, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-secondary {
 | 
				
			||||||
 | 
					  background-color: #a0a0a0;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 无记录状态 */
 | 
				
			||||||
 | 
					.no-records {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  padding: 60px 30px;
 | 
				
			||||||
 | 
					  background-color: var(--primary-light);
 | 
				
			||||||
 | 
					  border-radius: 15px;
 | 
				
			||||||
 | 
					  margin: 30px 0;
 | 
				
			||||||
 | 
					  box-shadow: inset 0 0 15px rgba(230, 134, 165, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-icon {
 | 
				
			||||||
 | 
					  font-size: 4em;
 | 
				
			||||||
 | 
					  color: var(--primary-color);
 | 
				
			||||||
 | 
					  margin-bottom: 20px;
 | 
				
			||||||
 | 
					  opacity: 0.7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-text {
 | 
				
			||||||
 | 
					  color: var(--text-primary);
 | 
				
			||||||
 | 
					  margin-bottom: 25px;
 | 
				
			||||||
 | 
					  font-size: 1.1rem;
 | 
				
			||||||
 | 
					  max-width: 450px;
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					  line-height: 1.6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页 */
 | 
				
			||||||
 | 
					.pagination-container {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  margin-top: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  gap: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item { margin: 0 2px; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-link {
 | 
				
			||||||
 | 
					  width: 36px;
 | 
				
			||||||
 | 
					  height: 36px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  border-radius: 50%;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  color: var(--text-primary);
 | 
				
			||||||
 | 
					  border: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					  font-size: 0.9rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-item.active .page-link,
 | 
				
			||||||
 | 
					.page-link:hover {
 | 
				
			||||||
 | 
					  background-color: var(--primary-color);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  border-color: var(--primary-color);
 | 
				
			||||||
 | 
					  box-shadow: 0 3px 8px rgba(230, 134, 165, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 模态框 */
 | 
				
			||||||
 | 
					.modal-dialog {
 | 
				
			||||||
 | 
					  max-width: 95%;
 | 
				
			||||||
 | 
					  width: 500px;
 | 
				
			||||||
 | 
					  margin: 1.75rem auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-content {
 | 
				
			||||||
 | 
					  border-radius: 15px;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-header {
 | 
				
			||||||
 | 
					  background-color: var(--primary-light);
 | 
				
			||||||
 | 
					  color: var(--primary-dark);
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					  padding: 15px 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-body {
 | 
				
			||||||
 | 
					  padding: 25px 20px;
 | 
				
			||||||
 | 
					  font-size: 1.1rem;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-footer {
 | 
				
			||||||
 | 
					  border-top: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					  padding: 15px 20px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  gap: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					  .container {
 | 
				
			||||||
 | 
					    width: 98% !important;
 | 
				
			||||||
 | 
					    padding: 1rem;
 | 
				
			||||||
 | 
					    margin: 0.5rem auto;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					  .tabs {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    background: none;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .tab {
 | 
				
			||||||
 | 
					    border-radius: 15px;
 | 
				
			||||||
 | 
					    margin-bottom: 8px;
 | 
				
			||||||
 | 
					    margin-right: 0;
 | 
				
			||||||
 | 
					    padding: 12px 15px;
 | 
				
			||||||
 | 
					    background-color: var(--primary-light);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .borrow-table {
 | 
				
			||||||
 | 
					    min-width: 700px; /* 确保在小屏幕上可以滚动 */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .book-cover img {
 | 
				
			||||||
 | 
					    width: 45px;
 | 
				
			||||||
 | 
					    height: 65px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										396
									
								
								app/static/css/overdue.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								app/static/css/overdue.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,396 @@
 | 
				
			|||||||
 | 
					/* overdue.css - 适合文艺少女的深棕色调设计 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: 'Georgia', 'Times New Roman', serif;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					    background-color: #fcf8f3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.container {
 | 
				
			||||||
 | 
					    background-color: #fff9f5;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 15px rgba(113, 66, 20, 0.1);
 | 
				
			||||||
 | 
					    padding: 25px;
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-title {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    font-family: 'Playfair Display', Georgia, 'Times New Roman', serif;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.d-flex {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.d-flex:after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    height: 2px;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, #d9c7b8, #8d6e63, #d9c7b8);
 | 
				
			||||||
 | 
					    margin-top: 15px;
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.alert-warning {
 | 
				
			||||||
 | 
					    background-color: #f9e8d0;
 | 
				
			||||||
 | 
					    border: 1px solid #ebd6ba;
 | 
				
			||||||
 | 
					    color: #8a6d3b;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 5px rgba(138, 109, 59, 0.1);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.alert-warning:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    height: 3px;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, #d4a76a, transparent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表格样式 */
 | 
				
			||||||
 | 
					.overdue-table {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border-collapse: separate;
 | 
				
			||||||
 | 
					    border-spacing: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 10px rgba(113, 66, 20, 0.05);
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue-table th,
 | 
				
			||||||
 | 
					.overdue-table td {
 | 
				
			||||||
 | 
					    padding: 15px 18px;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue-table th {
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue-item:hover {
 | 
				
			||||||
 | 
					    background-color: #f8f0e5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue-item:last-child td {
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img {
 | 
				
			||||||
 | 
					    width: 65px;
 | 
				
			||||||
 | 
					    height: 90px;
 | 
				
			||||||
 | 
					    object-fit: cover;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    box-shadow: 0 3px 8px rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    border: 2px solid #fff;
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-cover img:hover {
 | 
				
			||||||
 | 
					    transform: scale(1.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title {
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', 'Times New Roman', serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    transition: color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-title a:hover {
 | 
				
			||||||
 | 
					    color: #a66321;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.book-author {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin-top: 5px;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-info a {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    transition: color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-info a:hover {
 | 
				
			||||||
 | 
					    color: #a66321;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-nickname {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-contact {
 | 
				
			||||||
 | 
					    margin-top: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-contact a {
 | 
				
			||||||
 | 
					    color: #8d6e63;
 | 
				
			||||||
 | 
					    margin-right: 10px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-contact a:hover {
 | 
				
			||||||
 | 
					    color: #704214;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.email-link, .phone-link {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    padding: 4px 10px;
 | 
				
			||||||
 | 
					    font-size: 0.85em;
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    border-radius: 15px;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.email-link:hover, .phone-link:hover {
 | 
				
			||||||
 | 
					    background-color: #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text-danger {
 | 
				
			||||||
 | 
					    color: #a15950 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.overdue-days {
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 徽章 */
 | 
				
			||||||
 | 
					.badge {
 | 
				
			||||||
 | 
					    padding: 5px 12px;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    font-size: 0.85em;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-danger {
 | 
				
			||||||
 | 
					    background-color: #a15950;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-warning {
 | 
				
			||||||
 | 
					    background-color: #d4a76a;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-info {
 | 
				
			||||||
 | 
					    background-color: #6a8da9;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 按钮 */
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    padding: 8px 16px;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    letter-spacing: 0.3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-secondary {
 | 
				
			||||||
 | 
					    color: #704214;
 | 
				
			||||||
 | 
					    border-color: #d9c7b8;
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-secondary:hover {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    background-color: #8d6e63;
 | 
				
			||||||
 | 
					    border-color: #8d6e63;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success {
 | 
				
			||||||
 | 
					    background-color: #5b8a72;
 | 
				
			||||||
 | 
					    border-color: #5b8a72;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success:hover, .btn-success:focus {
 | 
				
			||||||
 | 
					    background-color: #4a7561;
 | 
				
			||||||
 | 
					    border-color: #4a7561;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-warning {
 | 
				
			||||||
 | 
					    background-color: #d4a76a;
 | 
				
			||||||
 | 
					    border-color: #d4a76a;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-warning:hover, .btn-warning:focus {
 | 
				
			||||||
 | 
					    background-color: #c29355;
 | 
				
			||||||
 | 
					    border-color: #c29355;
 | 
				
			||||||
 | 
					    color: #4a3728;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    border-color: #704214;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover, .btn-primary:focus {
 | 
				
			||||||
 | 
					    background-color: #5d3511;
 | 
				
			||||||
 | 
					    border-color: #5d3511;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.actions .btn {
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					    margin-bottom: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 空状态 */
 | 
				
			||||||
 | 
					.no-records {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    padding: 60px 20px;
 | 
				
			||||||
 | 
					    background-color: #f8f0e5;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    margin: 25px 0;
 | 
				
			||||||
 | 
					    border: 1px dashed #d9c7b8;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-records:before, .no-records:after {
 | 
				
			||||||
 | 
					    content: "❦";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    color: #d9c7b8;
 | 
				
			||||||
 | 
					    font-size: 24px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-records:before {
 | 
				
			||||||
 | 
					    top: 20px;
 | 
				
			||||||
 | 
					    left: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.no-records:after {
 | 
				
			||||||
 | 
					    bottom: 20px;
 | 
				
			||||||
 | 
					    right: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-icon {
 | 
				
			||||||
 | 
					    font-size: 4.5em;
 | 
				
			||||||
 | 
					    color: #5b8a72;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.empty-text {
 | 
				
			||||||
 | 
					    color: #5b8a72;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 分页 */
 | 
				
			||||||
 | 
					.pagination-container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin-top: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    border-color: #e8d9cb;
 | 
				
			||||||
 | 
					    margin: 0 3px;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-item.active .page-link {
 | 
				
			||||||
 | 
					    background-color: #704214;
 | 
				
			||||||
 | 
					    border-color: #704214;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pagination .page-link:hover {
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 模态框定制 */
 | 
				
			||||||
 | 
					.modal-content {
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    border: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 20px rgba(113, 66, 20, 0.15);
 | 
				
			||||||
 | 
					    background-color: #fff9f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-header {
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					    background-color: #f1e6dd;
 | 
				
			||||||
 | 
					    border-radius: 8px 8px 0 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-title {
 | 
				
			||||||
 | 
					    color: #5d3511;
 | 
				
			||||||
 | 
					    font-family: 'Georgia', 'Times New Roman', serif;
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-footer {
 | 
				
			||||||
 | 
					    border-top: 1px solid #e8d9cb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 装饰元素 */
 | 
				
			||||||
 | 
					.container:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    width: 150px;
 | 
				
			||||||
 | 
					    height: 150px;
 | 
				
			||||||
 | 
					    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cpath fill='%23d9c7b8' fill-opacity='0.2' d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
					    opacity: 0.3;
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式设计 */
 | 
				
			||||||
 | 
					@media (max-width: 992px) {
 | 
				
			||||||
 | 
					    .actions .btn {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        margin-bottom: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .no-records:before, .no-records:after {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .overdue-table {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        overflow-x: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .book-cover img {
 | 
				
			||||||
 | 
					        width: 50px;
 | 
				
			||||||
 | 
					        height: 70px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										636
									
								
								app/static/css/user-form.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										636
									
								
								app/static/css/user-form.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,636 @@
 | 
				
			|||||||
 | 
					/* 用户表单样式 - 甜美风格 */
 | 
				
			||||||
 | 
					.user-form-container {
 | 
				
			||||||
 | 
					    max-width: 850px;
 | 
				
			||||||
 | 
					    margin: 25px auto;
 | 
				
			||||||
 | 
					    padding: 0 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    padding-bottom: 15px;
 | 
				
			||||||
 | 
					    border-bottom: 2px solid #f8e6e8;
 | 
				
			||||||
 | 
					    animation: slideInDown 0.6s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-header h1 {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    font-size: 28px;
 | 
				
			||||||
 | 
					    color: #e75480; /* 粉红色调 */
 | 
				
			||||||
 | 
					    font-weight: 600;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-card {
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    border-radius: 12px;
 | 
				
			||||||
 | 
					    box-shadow: 0 5px 20px rgba(231, 84, 128, 0.08);
 | 
				
			||||||
 | 
					    padding: 30px;
 | 
				
			||||||
 | 
					    border: 1px solid #f8e6e8;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: visible;
 | 
				
			||||||
 | 
					    animation: fadeIn 0.7s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					    margin-bottom: 22px;
 | 
				
			||||||
 | 
					    animation: slideInRight 0.4s ease-out;
 | 
				
			||||||
 | 
					    animation-fill-mode: both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 为每个表单组添加延迟,创造波浪效果 */
 | 
				
			||||||
 | 
					.form-group:nth-child(1) { animation-delay: 0.1s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(2) { animation-delay: 0.2s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(3) { animation-delay: 0.3s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(4) { animation-delay: 0.4s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(5) { animation-delay: 0.5s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(6) { animation-delay: 0.6s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(7) { animation-delay: 0.7s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(8) { animation-delay: 0.8s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(9) { animation-delay: 0.9s; }
 | 
				
			||||||
 | 
					.form-group:nth-child(10) { animation-delay: 1.0s; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.required label:after {
 | 
				
			||||||
 | 
					    content: " *";
 | 
				
			||||||
 | 
					    color: #ff6b8b;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group label {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-bottom: 8px;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    color: #5d5d5d;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group:hover label {
 | 
				
			||||||
 | 
					    color: #e75480;
 | 
				
			||||||
 | 
					    transform: translateX(3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 12px 15px;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    background-clip: padding-box;
 | 
				
			||||||
 | 
					    border: 1.5px solid #ffd1dc; /* 淡粉色边框 */
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control:focus {
 | 
				
			||||||
 | 
					    border-color: #ff8da1;
 | 
				
			||||||
 | 
					    outline: 0;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 3px rgba(255, 141, 161, 0.25);
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-control::placeholder {
 | 
				
			||||||
 | 
					    color: #bbb;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.password-field {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.toggle-password {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    right: 12px;
 | 
				
			||||||
 | 
					    top: 50%;
 | 
				
			||||||
 | 
					    transform: translateY(-50%);
 | 
				
			||||||
 | 
					    background: none;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    color: #ff8da1;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.toggle-password:hover {
 | 
				
			||||||
 | 
					    color: #e75480;
 | 
				
			||||||
 | 
					    transform: translateY(-50%) scale(1.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.input-with-button {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.input-with-button .form-control {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.input-with-button .btn {
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-text {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-top: 6px;
 | 
				
			||||||
 | 
					    font-size: 13.5px;
 | 
				
			||||||
 | 
					    color: #888;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-text.text-danger {
 | 
				
			||||||
 | 
					    color: #ff5c77;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-text.text-success {
 | 
				
			||||||
 | 
					    color: #7ac98f;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    animation: pulse 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-actions {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 15px;
 | 
				
			||||||
 | 
					    margin-top: 35px;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    animation: fadeInUp 0.8s ease-out;
 | 
				
			||||||
 | 
					    animation-delay: 1.2s;
 | 
				
			||||||
 | 
					    animation-fill-mode: both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					    user-select: none;
 | 
				
			||||||
 | 
					    border: 1.5px solid transparent;
 | 
				
			||||||
 | 
					    padding: 10px 22px;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    border-radius: 25px; /* 圆润按钮 */
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    letter-spacing: 0.3px;
 | 
				
			||||||
 | 
					    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 按钮波纹效果 */
 | 
				
			||||||
 | 
					.btn:after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 50%;
 | 
				
			||||||
 | 
					    left: 50%;
 | 
				
			||||||
 | 
					    width: 5px;
 | 
				
			||||||
 | 
					    height: 5px;
 | 
				
			||||||
 | 
					    background: rgba(255, 255, 255, 0.5);
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    border-radius: 100%;
 | 
				
			||||||
 | 
					    transform: scale(1, 1) translate(-50%);
 | 
				
			||||||
 | 
					    transform-origin: 50% 50%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn:focus:not(:active)::after {
 | 
				
			||||||
 | 
					    animation: ripple 1s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes ripple {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: scale(0, 0);
 | 
				
			||||||
 | 
					        opacity: 0.5;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    20% {
 | 
				
			||||||
 | 
					        transform: scale(25, 25);
 | 
				
			||||||
 | 
					        opacity: 0.3;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: scale(50, 50);
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    background-color: #ff8da1;
 | 
				
			||||||
 | 
					    border-color: #ff8da1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    background-color: #ff7389;
 | 
				
			||||||
 | 
					    border-color: #ff7389;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 8px rgba(255, 141, 161, 0.3);
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:active {
 | 
				
			||||||
 | 
					    transform: translateY(-1px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-secondary {
 | 
				
			||||||
 | 
					    color: #777;
 | 
				
			||||||
 | 
					    background-color: #f8f9fa;
 | 
				
			||||||
 | 
					    border-color: #e6e6e6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-secondary:hover {
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    background-color: #f1f1f1;
 | 
				
			||||||
 | 
					    border-color: #d9d9d9;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					    transform: translateY(-3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-primary {
 | 
				
			||||||
 | 
					    color: #ff8da1;
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					    border-color: #ff8da1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline-primary:hover {
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    background-color: #ff8da1;
 | 
				
			||||||
 | 
					    border-color: #ff8da1;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 8px rgba(255, 141, 161, 0.2);
 | 
				
			||||||
 | 
					    transform: translateY(-2px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn i {
 | 
				
			||||||
 | 
					    margin-right: 6px;
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn:hover i {
 | 
				
			||||||
 | 
					    transform: translateX(-3px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 禁用状态 */
 | 
				
			||||||
 | 
					.btn:disabled,
 | 
				
			||||||
 | 
					.btn.disabled {
 | 
				
			||||||
 | 
					    opacity: 0.65;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					    transform: none !important;
 | 
				
			||||||
 | 
					    box-shadow: none !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 提示信息 */
 | 
				
			||||||
 | 
					.alert {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    padding: 14px 20px;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					    border: 1px solid transparent;
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.alert-danger {
 | 
				
			||||||
 | 
					    color: #ff5c77;
 | 
				
			||||||
 | 
					    background-color: #fff0f3;
 | 
				
			||||||
 | 
					    border-color: #ffe0e5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 装饰元素 */
 | 
				
			||||||
 | 
					.form-card::before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -15px;
 | 
				
			||||||
 | 
					    right: 30px;
 | 
				
			||||||
 | 
					    width: 40px;
 | 
				
			||||||
 | 
					    height: 40px;
 | 
				
			||||||
 | 
					    background-color: #ffeaef;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					    animation: float 6s ease-in-out infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-card::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: -20px;
 | 
				
			||||||
 | 
					    left: 50px;
 | 
				
			||||||
 | 
					    width: 60px;
 | 
				
			||||||
 | 
					    height: 60px;
 | 
				
			||||||
 | 
					    background-color: #ffeaef;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					    opacity: 0.6;
 | 
				
			||||||
 | 
					    animation: float 7s ease-in-out infinite reverse;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 修复选择框问题 */
 | 
				
			||||||
 | 
					s/* 专门修复下拉框文字显示问题 */
 | 
				
			||||||
 | 
					select.form-control {
 | 
				
			||||||
 | 
					    /* 保持一致的外观 */
 | 
				
			||||||
 | 
					    appearance: none;
 | 
				
			||||||
 | 
					    -webkit-appearance: none;
 | 
				
			||||||
 | 
					    -moz-appearance: none;
 | 
				
			||||||
 | 
					    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ff8da1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: right 12px center;
 | 
				
			||||||
 | 
					    background-size: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* 修正文字显示问题 */
 | 
				
			||||||
 | 
					    padding: 12px 40px 12px 15px; /* 增加右侧内边距,确保文字不被箭头遮挡 */
 | 
				
			||||||
 | 
					    text-overflow: ellipsis; /* 如果文字太长会显示省略号 */
 | 
				
			||||||
 | 
					    white-space: nowrap; /* 防止文本换行 */
 | 
				
			||||||
 | 
					    color: #555 !important; /* 强制文本颜色 */
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 确保选定的选项能被完整显示 */
 | 
				
			||||||
 | 
					select.form-control option {
 | 
				
			||||||
 | 
					    padding: 10px 15px;
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 针对特定浏览器的修复 */
 | 
				
			||||||
 | 
					@-moz-document url-prefix() {
 | 
				
			||||||
 | 
					    select.form-control {
 | 
				
			||||||
 | 
					        color: #555;
 | 
				
			||||||
 | 
					        text-indent: 0;
 | 
				
			||||||
 | 
					        text-overflow: clip;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 针对Safari的修复 */
 | 
				
			||||||
 | 
					@media screen and (-webkit-min-device-pixel-ratio: 0) {
 | 
				
			||||||
 | 
					    select.form-control {
 | 
				
			||||||
 | 
					        text-indent: 1px;
 | 
				
			||||||
 | 
					        text-overflow: clip;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 设置选中文本的样式 */
 | 
				
			||||||
 | 
					select.form-control:focus option:checked {
 | 
				
			||||||
 | 
					    background: #ffeaef;
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 修复IE特定问题 */
 | 
				
			||||||
 | 
					select::-ms-expand {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 确保选项在下拉框中正确展示 */
 | 
				
			||||||
 | 
					select.form-control option {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 解决Chrome中的问题 */
 | 
				
			||||||
 | 
					@media screen and (-webkit-min-device-pixel-ratio: 0) {
 | 
				
			||||||
 | 
					    select.form-control {
 | 
				
			||||||
 | 
					        border-radius: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 更明确地设置选择状态的样式 */
 | 
				
			||||||
 | 
					select.form-control {
 | 
				
			||||||
 | 
					    border: 1.5px solid #ffd1dc;
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select.form-control:focus {
 | 
				
			||||||
 | 
					    border-color: #ff8da1;
 | 
				
			||||||
 | 
					    outline: 0;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 3px rgba(255, 141, 161, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 尝试不同的方式设置下拉箭头 */
 | 
				
			||||||
 | 
					.select-wrapper {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.select-wrapper::after {
 | 
				
			||||||
 | 
					    content: '⌄';
 | 
				
			||||||
 | 
					    font-size: 24px;
 | 
				
			||||||
 | 
					    color: #ff8da1;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    right: 15px;
 | 
				
			||||||
 | 
					    top: 50%;
 | 
				
			||||||
 | 
					    transform: translateY(-50%);
 | 
				
			||||||
 | 
					    pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 移除自定义背景图,改用伪元素作为箭头 */
 | 
				
			||||||
 | 
					select.form-control {
 | 
				
			||||||
 | 
					    background-image: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 美化表单分组 */
 | 
				
			||||||
 | 
					.form-card {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					    transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group:hover {
 | 
				
			||||||
 | 
					    transform: translateX(5px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 甜美风格的表单组分隔线 */
 | 
				
			||||||
 | 
					.form-group:not(:last-child):after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    height: 1px;
 | 
				
			||||||
 | 
					    width: 0;
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, transparent, #ffe0e8, transparent);
 | 
				
			||||||
 | 
					    margin-top: 22px;
 | 
				
			||||||
 | 
					    transition: width 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group:not(:last-child):hover:after {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 必填项标记美化 */
 | 
				
			||||||
 | 
					.form-group.required label {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.required label:after {
 | 
				
			||||||
 | 
					    content: " *";
 | 
				
			||||||
 | 
					    color: #ff6b8b;
 | 
				
			||||||
 | 
					    font-size: 18px;
 | 
				
			||||||
 | 
					    line-height: 0;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    top: 5px;
 | 
				
			||||||
 | 
					    transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.required:hover label:after {
 | 
				
			||||||
 | 
					    color: #ff3958;
 | 
				
			||||||
 | 
					    transform: scale(1.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 美化滚动条 */
 | 
				
			||||||
 | 
					::-webkit-scrollbar {
 | 
				
			||||||
 | 
					    width: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::-webkit-scrollbar-track {
 | 
				
			||||||
 | 
					    background: #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::-webkit-scrollbar-thumb {
 | 
				
			||||||
 | 
					    background-color: #ffc0cb;
 | 
				
			||||||
 | 
					    border-radius: 20px;
 | 
				
			||||||
 | 
					    border: 2px solid #fff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::-webkit-scrollbar-thumb:hover {
 | 
				
			||||||
 | 
					    background-color: #ff8da1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 添加动画 */
 | 
				
			||||||
 | 
					@keyframes fadeIn {
 | 
				
			||||||
 | 
					    from { opacity: 0; }
 | 
				
			||||||
 | 
					    to { opacity: 1; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes slideInRight {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateX(20px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateX(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes slideInDown {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateY(-20px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateY(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes fadeInUp {
 | 
				
			||||||
 | 
					    from { opacity: 0; transform: translateY(20px); }
 | 
				
			||||||
 | 
					    to { opacity: 1; transform: translateY(0); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes float {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: translateY(0px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    50% {
 | 
				
			||||||
 | 
					        transform: translateY(-15px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: translateY(0px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes pulse {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: scale(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    50% {
 | 
				
			||||||
 | 
					        transform: scale(1.05);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: scale(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes shake {
 | 
				
			||||||
 | 
					    10%, 90% {
 | 
				
			||||||
 | 
					        transform: translateX(-1px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    20%, 80% {
 | 
				
			||||||
 | 
					        transform: translateX(2px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    30%, 50%, 70% {
 | 
				
			||||||
 | 
					        transform: translateX(-3px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    40%, 60% {
 | 
				
			||||||
 | 
					        transform: translateX(3px);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 响应式设计 */
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					    .form-actions {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .input-with-button {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header {
 | 
				
			||||||
 | 
					        flex-direction: column;
 | 
				
			||||||
 | 
					        align-items: flex-start;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .page-header .actions {
 | 
				
			||||||
 | 
					        margin-top: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .btn {
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 表单光影效果 */
 | 
				
			||||||
 | 
					.form-card {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-card:before, .form-card:after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 移入表单时添加光晕效果 */
 | 
				
			||||||
 | 
					.form-card:hover:before {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -50%;
 | 
				
			||||||
 | 
					    left: -50%;
 | 
				
			||||||
 | 
					    width: 200%;
 | 
				
			||||||
 | 
					    height: 200%;
 | 
				
			||||||
 | 
					    background: radial-gradient(circle, rgba(255,232,238,0.3) 0%, rgba(255,255,255,0) 70%);
 | 
				
			||||||
 | 
					    animation: glowEffect 2s infinite linear;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes glowEffect {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					        transform: rotate(0deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					        transform: rotate(360deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 输入焦点时的动画 */
 | 
				
			||||||
 | 
					.form-control:focus {
 | 
				
			||||||
 | 
					    animation: focusPulse 1s infinite alternate;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes focusPulse {
 | 
				
			||||||
 | 
					    from {
 | 
				
			||||||
 | 
					        box-shadow: 0 0 0 3px rgba(255, 141, 161, 0.25);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    to {
 | 
				
			||||||
 | 
					        box-shadow: 0 0 0 5px rgba(255, 141, 161, 0.15);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -127,7 +127,7 @@ $(document).ready(function() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 初始化表单验证
 | 
					    // 初始化表单验证
 | 
				
			||||||
    ffunction initFormValidation() {
 | 
					    function initFormValidation() {
 | 
				
			||||||
        $('#bookForm').on('submit', function(e) {
 | 
					        $('#bookForm').on('submit', function(e) {
 | 
				
			||||||
            // 如果表单正在提交中,阻止重复提交
 | 
					            // 如果表单正在提交中,阻止重复提交
 | 
				
			||||||
            if (isSubmitting) {
 | 
					            if (isSubmitting) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										176
									
								
								app/static/js/book-edit.js 
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								app/static/js/book-edit.js 
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
				
			|||||||
 | 
					// 用于调试
 | 
				
			||||||
 | 
					console.log("ISBN验证脚本已加载 v2.0");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$(document).ready(function() {
 | 
				
			||||||
 | 
					    console.log("DOM已加载,开始设置ISBN验证");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 获取ISBN输入框
 | 
				
			||||||
 | 
					    const isbnInput = $("#isbn");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isbnInput.length === 0) {
 | 
				
			||||||
 | 
					        console.error("找不到ISBN输入字段!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("找到ISBN输入框:", isbnInput.val());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加ISBN帮助文本和错误提示
 | 
				
			||||||
 | 
					    isbnInput.after('<div class="invalid-feedback" id="isbn-error">请输入有效的10位或13位ISBN号码</div>');
 | 
				
			||||||
 | 
					    isbnInput.after('<small class="form-text text-muted">请输入有效的ISBN-10或ISBN-13格式</small>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 验证ISBN函数
 | 
				
			||||||
 | 
					    function validateISBN(isbn) {
 | 
				
			||||||
 | 
					        // 空值视为有效
 | 
				
			||||||
 | 
					        if (!isbn || isbn.trim() === '') return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 移除所有连字符和空格
 | 
				
			||||||
 | 
					        isbn = isbn.replace(/[-\s]/g, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("验证ISBN:", isbn, "长度:", isbn.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 长度检查
 | 
				
			||||||
 | 
					        if (isbn.length !== 10 && isbn.length !== 13) {
 | 
				
			||||||
 | 
					            console.log("ISBN长度无效");
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ISBN-10验证
 | 
				
			||||||
 | 
					        if (isbn.length === 10) {
 | 
				
			||||||
 | 
					            // 检查前9位是否为数字,最后一位可以是X
 | 
				
			||||||
 | 
					            if (!/^\d{9}[\dXx]$/.test(isbn)) {
 | 
				
			||||||
 | 
					                console.log("ISBN-10格式错误");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let sum = 0;
 | 
				
			||||||
 | 
					            for (let i = 0; i < 9; i++) {
 | 
				
			||||||
 | 
					                sum += parseInt(isbn[i]) * (10 - i);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 处理校验位
 | 
				
			||||||
 | 
					            let checkDigit = isbn[9].toUpperCase() === 'X' ? 10 : parseInt(isbn[9]);
 | 
				
			||||||
 | 
					            sum += checkDigit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let valid = (sum % 11 === 0);
 | 
				
			||||||
 | 
					            console.log("ISBN-10校验结果:", valid);
 | 
				
			||||||
 | 
					            return valid;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ISBN-13验证
 | 
				
			||||||
 | 
					        if (isbn.length === 13) {
 | 
				
			||||||
 | 
					            // 检查是否全是数字
 | 
				
			||||||
 | 
					            if (!/^\d{13}$/.test(isbn)) {
 | 
				
			||||||
 | 
					                console.log("ISBN-13必须全是数字");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let sum = 0;
 | 
				
			||||||
 | 
					            for (let i = 0; i < 12; i++) {
 | 
				
			||||||
 | 
					                sum += parseInt(isbn[i]) * (i % 2 === 0 ? 1 : 3);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let checkDigit = (10 - (sum % 10)) % 10;
 | 
				
			||||||
 | 
					            let valid = (checkDigit === parseInt(isbn[12]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log("ISBN-13校验结果:", valid);
 | 
				
			||||||
 | 
					            return valid;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加输入事件处理
 | 
				
			||||||
 | 
					    isbnInput.on('input', function() {
 | 
				
			||||||
 | 
					        const value = $(this).val().trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (value === '') {
 | 
				
			||||||
 | 
					            // 不验证空值
 | 
				
			||||||
 | 
					            $(this).removeClass('is-invalid is-valid');
 | 
				
			||||||
 | 
					        } else if (validateISBN(value)) {
 | 
				
			||||||
 | 
					            $(this).removeClass('is-invalid').addClass('is-valid');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $(this).removeClass('is-valid').addClass('is-invalid');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 表单提交验证
 | 
				
			||||||
 | 
					    const form = $(".book-form");
 | 
				
			||||||
 | 
					    form.on('submit', function(event) {
 | 
				
			||||||
 | 
					        const isbnValue = isbnInput.val().trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("表单提交,ISBN值:", isbnValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 仅当有ISBN且无效时才阻止提交
 | 
				
			||||||
 | 
					        if (isbnValue !== '' && !validateISBN(isbnValue)) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            event.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            isbnInput.addClass('is-invalid');
 | 
				
			||||||
 | 
					            isbnInput.focus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            alert('请输入有效的ISBN号码,或将此字段留空');
 | 
				
			||||||
 | 
					            console.log("表单提交被阻止 - ISBN无效");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("表单提交继续 - ISBN有效或为空");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 处理封面图片预览
 | 
				
			||||||
 | 
					    $('#cover').change(function() {
 | 
				
			||||||
 | 
					        const file = this.files[0];
 | 
				
			||||||
 | 
					        if (file) {
 | 
				
			||||||
 | 
					            const reader = new FileReader();
 | 
				
			||||||
 | 
					            reader.onload = function(e) {
 | 
				
			||||||
 | 
					                $('#coverPreview').html(`<img src="${e.target.result}" class="cover-image" alt="新封面预览">`);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            reader.readAsDataURL(file);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 默认回退
 | 
				
			||||||
 | 
					            if (typeof bookCoverUrl !== 'undefined' && bookCoverUrl) {
 | 
				
			||||||
 | 
					                $('#coverPreview').html(`<img src="${bookCoverUrl}" class="cover-image" alt="${bookTitle || '图书封面'}">`);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                $('#coverPreview').html(`
 | 
				
			||||||
 | 
					                    <div class="no-cover-placeholder">
 | 
				
			||||||
 | 
					                        <i class="fas fa-book"></i>
 | 
				
			||||||
 | 
					                        <span>暂无封面</span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                `);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 在页面加载时初始验证现有ISBN
 | 
				
			||||||
 | 
					    if (isbnInput.val()) {
 | 
				
			||||||
 | 
					        isbnInput.trigger('input');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加帮助样式
 | 
				
			||||||
 | 
					    $("<style>")
 | 
				
			||||||
 | 
					        .prop("type", "text/css")
 | 
				
			||||||
 | 
					        .html(`
 | 
				
			||||||
 | 
					            .is-invalid {
 | 
				
			||||||
 | 
					                border-color: #dc3545 !important;
 | 
				
			||||||
 | 
					                background-color: #fff8f8 !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .is-valid {
 | 
				
			||||||
 | 
					                border-color: #28a745 !important;
 | 
				
			||||||
 | 
					                background-color: #f8fff8 !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .invalid-feedback {
 | 
				
			||||||
 | 
					                display: none;
 | 
				
			||||||
 | 
					                color: #dc3545;
 | 
				
			||||||
 | 
					                font-size: 80%;
 | 
				
			||||||
 | 
					                margin-top: 0.25rem;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .is-invalid + .invalid-feedback,
 | 
				
			||||||
 | 
					            .is-invalid ~ .invalid-feedback {
 | 
				
			||||||
 | 
					                display: block !important;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        `)
 | 
				
			||||||
 | 
					        .appendTo("head");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										244
									
								
								app/static/js/borrow_management.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								app/static/js/borrow_management.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,244 @@
 | 
				
			|||||||
 | 
					// borrow_management.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 归还图书功能
 | 
				
			||||||
 | 
					    const returnButtons = document.querySelectorAll('.return-btn');
 | 
				
			||||||
 | 
					    const returnModal = document.getElementById('returnModal');
 | 
				
			||||||
 | 
					    const returnBookTitle = document.getElementById('returnBookTitle');
 | 
				
			||||||
 | 
					    const confirmReturnButton = document.getElementById('confirmReturn');
 | 
				
			||||||
 | 
					    let currentBorrowId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    returnButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            returnBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmReturnButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送归还请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/return/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 重新加载页面以更新借阅状态
 | 
				
			||||||
 | 
					                setTimeout(() => window.location.reload(), 1500);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 续借图书功能
 | 
				
			||||||
 | 
					    const renewButtons = document.querySelectorAll('.renew-btn');
 | 
				
			||||||
 | 
					    const renewModal = document.getElementById('renewModal');
 | 
				
			||||||
 | 
					    const renewBookTitle = document.getElementById('renewBookTitle');
 | 
				
			||||||
 | 
					    const confirmRenewButton = document.getElementById('confirmRenew');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renewButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            renewBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#renewModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmRenewButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送续借请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/renew/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#renewModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 重新加载页面以更新借阅状态
 | 
				
			||||||
 | 
					                setTimeout(() => window.location.reload(), 1500);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#renewModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 逾期通知功能
 | 
				
			||||||
 | 
					    const notifyButtons = document.querySelectorAll('.notify-btn');
 | 
				
			||||||
 | 
					    const notifyModal = document.getElementById('notifyModal');
 | 
				
			||||||
 | 
					    const notifyBookTitle = document.getElementById('notifyBookTitle');
 | 
				
			||||||
 | 
					    const confirmNotifyButton = document.getElementById('confirmNotify');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notifyButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            notifyBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmNotifyButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送通知请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/overdue/notify/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 禁用已点击的通知按钮
 | 
				
			||||||
 | 
					                document.querySelector(`.notify-btn[data-id="${currentBorrowId}"]`).disabled = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 图书搜索功能
 | 
				
			||||||
 | 
					    const bookSearchInput = document.getElementById('bookSearch');
 | 
				
			||||||
 | 
					    const searchBookBtn = document.getElementById('searchBookBtn');
 | 
				
			||||||
 | 
					    const bookSelect = document.getElementById('bookSelect');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function searchBooks() {
 | 
				
			||||||
 | 
					        const searchTerm = bookSearchInput.value.trim();
 | 
				
			||||||
 | 
					        if (searchTerm.length < 2) {
 | 
				
			||||||
 | 
					            showAlert('warning', '请输入至少2个字符进行搜索');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 清空当前选项
 | 
				
			||||||
 | 
					        bookSelect.innerHTML = '<option value="">正在搜索...</option>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送搜索请求
 | 
				
			||||||
 | 
					        fetch(`/book/api/search?q=${encodeURIComponent(searchTerm)}`)
 | 
				
			||||||
 | 
					            .then(response => response.json())
 | 
				
			||||||
 | 
					            .then(data => {
 | 
				
			||||||
 | 
					                bookSelect.innerHTML = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (data.success && data.books.length > 0) {
 | 
				
			||||||
 | 
					                    // 添加找到的图书
 | 
				
			||||||
 | 
					                    data.books.forEach(book => {
 | 
				
			||||||
 | 
					                        const option = document.createElement('option');
 | 
				
			||||||
 | 
					                        option.value = book.id;
 | 
				
			||||||
 | 
					                        option.textContent = `${book.title} - ${book.author} (库存: ${book.stock})`;
 | 
				
			||||||
 | 
					                        // 如果库存为0,禁用该选项
 | 
				
			||||||
 | 
					                        if (book.stock <= 0) {
 | 
				
			||||||
 | 
					                            option.disabled = true;
 | 
				
			||||||
 | 
					                            option.textContent += ' [无库存]';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        bookSelect.appendChild(option);
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 未找到图书
 | 
				
			||||||
 | 
					                    const option = document.createElement('option');
 | 
				
			||||||
 | 
					                    option.value = '';
 | 
				
			||||||
 | 
					                    option.textContent = '未找到相关图书';
 | 
				
			||||||
 | 
					                    bookSelect.appendChild(option);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(error => {
 | 
				
			||||||
 | 
					                console.error('搜索图书时出错:', error);
 | 
				
			||||||
 | 
					                bookSelect.innerHTML = '<option value="">搜索失败,请重试</option>';
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 绑定搜索按钮点击事件
 | 
				
			||||||
 | 
					    searchBookBtn.addEventListener('click', searchBooks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 绑定回车键搜索
 | 
				
			||||||
 | 
					    bookSearchInput.addEventListener('keypress', function(e) {
 | 
				
			||||||
 | 
					        if (e.key === 'Enter') {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            searchBooks();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 显示提示消息
 | 
				
			||||||
 | 
					    function showAlert(type, message) {
 | 
				
			||||||
 | 
					        const alertDiv = document.createElement('div');
 | 
				
			||||||
 | 
					        alertDiv.className = `alert alert-${type} alert-dismissible fade show fixed-top mx-auto mt-3`;
 | 
				
			||||||
 | 
					        alertDiv.style.maxWidth = '500px';
 | 
				
			||||||
 | 
					        alertDiv.style.zIndex = '9999';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        alertDiv.innerHTML = `
 | 
				
			||||||
 | 
					            ${message}
 | 
				
			||||||
 | 
					            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
 | 
				
			||||||
 | 
					                <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.body.appendChild(alertDiv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 3秒后自动消失
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            alertDiv.remove();
 | 
				
			||||||
 | 
					        }, 3000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										292
									
								
								app/static/js/browse.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								app/static/js/browse.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,292 @@
 | 
				
			|||||||
 | 
					// 图书浏览页面脚本
 | 
				
			||||||
 | 
					$(document).ready(function() {
 | 
				
			||||||
 | 
					    // 分类筛选下拉菜单
 | 
				
			||||||
 | 
					    $('.category-filter-toggle').click(function() {
 | 
				
			||||||
 | 
					        $(this).toggleClass('active');
 | 
				
			||||||
 | 
					        $('.category-filter-dropdown').toggleClass('show');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 点击外部关闭下拉菜单
 | 
				
			||||||
 | 
					    $(document).click(function(e) {
 | 
				
			||||||
 | 
					        if (!$(e.target).closest('.category-filters').length) {
 | 
				
			||||||
 | 
					            $('.category-filter-dropdown').removeClass('show');
 | 
				
			||||||
 | 
					            $('.category-filter-toggle').removeClass('active');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 处理借阅图书
 | 
				
			||||||
 | 
					    let bookIdToBorrow = null;
 | 
				
			||||||
 | 
					    let bookTitleToBorrow = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('.borrow-book').click(function(e) {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        bookIdToBorrow = $(this).data('id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 获取图书标题
 | 
				
			||||||
 | 
					        const bookCard = $(this).closest('.book-card');
 | 
				
			||||||
 | 
					        bookTitleToBorrow = bookCard.find('.book-title').text();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $('#borrowBookTitle').text(bookTitleToBorrow);
 | 
				
			||||||
 | 
					        $('#borrowModal').modal('show');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#confirmBorrow').click(function() {
 | 
				
			||||||
 | 
					        if (!bookIdToBorrow) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 禁用按钮防止重复提交
 | 
				
			||||||
 | 
					        $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 处理中...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $.ajax({
 | 
				
			||||||
 | 
					            url: `/borrow/add/${bookIdToBorrow}`,
 | 
				
			||||||
 | 
					            type: 'POST',
 | 
				
			||||||
 | 
					            success: function(response) {
 | 
				
			||||||
 | 
					                $('#borrowModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (response.success) {
 | 
				
			||||||
 | 
					                    showNotification(response.message, 'success');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // 更新UI显示
 | 
				
			||||||
 | 
					                    const bookCard = $(`.book-card[data-id="${bookIdToBorrow}"]`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // 更改可借状态
 | 
				
			||||||
 | 
					                    bookCard.find('.book-ribbon span').removeClass('available').addClass('unavailable').text('已借出');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // 更改借阅按钮
 | 
				
			||||||
 | 
					                    bookCard.find('.btn-borrow').replaceWith(`
 | 
				
			||||||
 | 
					                        <button class="btn-borrow disabled" disabled>
 | 
				
			||||||
 | 
					                            <i class="fas fa-check-circle"></i> 已借出
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    `);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // 创建借阅成功动画
 | 
				
			||||||
 | 
					                    const successOverlay = $('<div class="borrow-success-overlay"><i class="fas fa-check-circle"></i><span>借阅成功</span></div>');
 | 
				
			||||||
 | 
					                    bookCard.append(successOverlay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    setTimeout(() => {
 | 
				
			||||||
 | 
					                        successOverlay.fadeOut(500, function() {
 | 
				
			||||||
 | 
					                            $(this).remove();
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }, 2000);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    showNotification(response.message, 'error');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // 恢复按钮状态
 | 
				
			||||||
 | 
					                $('#confirmBorrow').prop('disabled', false).html('确认借阅');
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            error: function() {
 | 
				
			||||||
 | 
					                $('#borrowModal').modal('hide');
 | 
				
			||||||
 | 
					                showNotification('借阅操作失败,请稍后重试', 'error');
 | 
				
			||||||
 | 
					                $('#confirmBorrow').prop('disabled', false).html('确认借阅');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 清除模态框数据
 | 
				
			||||||
 | 
					    $('#borrowModal').on('hidden.bs.modal', function() {
 | 
				
			||||||
 | 
					        bookIdToBorrow = null;
 | 
				
			||||||
 | 
					        bookTitleToBorrow = '';
 | 
				
			||||||
 | 
					        $('#borrowBookTitle').text('');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 动态添加动画CSS
 | 
				
			||||||
 | 
					    const animationCSS = `
 | 
				
			||||||
 | 
					        .borrow-success-overlay {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            right: 0;
 | 
				
			||||||
 | 
					            bottom: 0;
 | 
				
			||||||
 | 
					            background-color: rgba(102, 126, 234, 0.9);
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            flex-direction: column;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            justify-content: center;
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            font-weight: 600;
 | 
				
			||||||
 | 
					            border-radius: 10px;
 | 
				
			||||||
 | 
					            z-index: 10;
 | 
				
			||||||
 | 
					            animation: fadeIn 0.3s;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .borrow-success-overlay i {
 | 
				
			||||||
 | 
					            font-size: 40px;
 | 
				
			||||||
 | 
					            margin-bottom: 10px;
 | 
				
			||||||
 | 
					            animation: scaleIn 0.5s;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes fadeIn {
 | 
				
			||||||
 | 
					            from { opacity: 0; }
 | 
				
			||||||
 | 
					            to { opacity: 1; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes scaleIn {
 | 
				
			||||||
 | 
					            from { transform: scale(0); }
 | 
				
			||||||
 | 
					            to { transform: scale(1); }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('<style>').text(animationCSS).appendTo('head');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 显示通知
 | 
				
			||||||
 | 
					    function showNotification(message, type) {
 | 
				
			||||||
 | 
					        // 移除可能存在的旧通知
 | 
				
			||||||
 | 
					        $('.notification-alert').remove();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const alertClass = type === 'success' ? 'notification-success' : 'notification-error';
 | 
				
			||||||
 | 
					        const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const notification = `
 | 
				
			||||||
 | 
					            <div class="notification-alert ${alertClass}">
 | 
				
			||||||
 | 
					                <div class="notification-icon">
 | 
				
			||||||
 | 
					                    <i class="fas ${iconClass}"></i>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="notification-message">${message}</div>
 | 
				
			||||||
 | 
					                <button class="notification-close">
 | 
				
			||||||
 | 
					                    <i class="fas fa-times"></i>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $('body').append(notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 显示通知
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            $('.notification-alert').addClass('show');
 | 
				
			||||||
 | 
					        }, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 通知自动关闭
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            $('.notification-alert').removeClass('show');
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                $('.notification-alert').remove();
 | 
				
			||||||
 | 
					            }, 300);
 | 
				
			||||||
 | 
					        }, 4000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 点击关闭按钮
 | 
				
			||||||
 | 
					        $('.notification-close').click(function() {
 | 
				
			||||||
 | 
					            $(this).closest('.notification-alert').removeClass('show');
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                $(this).closest('.notification-alert').remove();
 | 
				
			||||||
 | 
					            }, 300);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加通知样式
 | 
				
			||||||
 | 
					    const notificationCSS = `
 | 
				
			||||||
 | 
					        .notification-alert {
 | 
				
			||||||
 | 
					            position: fixed;
 | 
				
			||||||
 | 
					            top: 20px;
 | 
				
			||||||
 | 
					            right: 20px;
 | 
				
			||||||
 | 
					            min-width: 300px;
 | 
				
			||||||
 | 
					            max-width: 400px;
 | 
				
			||||||
 | 
					            background-color: white;
 | 
				
			||||||
 | 
					            border-radius: 10px;
 | 
				
			||||||
 | 
					            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            padding: 15px;
 | 
				
			||||||
 | 
					            transform: translateX(calc(100% + 20px));
 | 
				
			||||||
 | 
					            transition: transform 0.3s ease;
 | 
				
			||||||
 | 
					            z-index: 9999;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-alert.show {
 | 
				
			||||||
 | 
					            transform: translateX(0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-success {
 | 
				
			||||||
 | 
					            border-left: 4px solid #4caf50;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-error {
 | 
				
			||||||
 | 
					            border-left: 4px solid #f44336;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-icon {
 | 
				
			||||||
 | 
					            margin-right: 15px;
 | 
				
			||||||
 | 
					            font-size: 24px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-success .notification-icon {
 | 
				
			||||||
 | 
					            color: #4caf50;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-error .notification-icon {
 | 
				
			||||||
 | 
					            color: #f44336;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-message {
 | 
				
			||||||
 | 
					            flex: 1;
 | 
				
			||||||
 | 
					            font-size: 0.95rem;
 | 
				
			||||||
 | 
					            color: #3c4858;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-close {
 | 
				
			||||||
 | 
					            background: none;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            color: #a0aec0;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            padding: 5px;
 | 
				
			||||||
 | 
					            margin-left: 10px;
 | 
				
			||||||
 | 
					            font-size: 0.8rem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .notification-close:hover {
 | 
				
			||||||
 | 
					            color: #4a5568;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @media (max-width: 576px) {
 | 
				
			||||||
 | 
					            .notification-alert {
 | 
				
			||||||
 | 
					                top: auto;
 | 
				
			||||||
 | 
					                bottom: 20px;
 | 
				
			||||||
 | 
					                left: 20px;
 | 
				
			||||||
 | 
					                right: 20px;
 | 
				
			||||||
 | 
					                min-width: auto;
 | 
				
			||||||
 | 
					                max-width: none;
 | 
				
			||||||
 | 
					                transform: translateY(calc(100% + 20px));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            .notification-alert.show {
 | 
				
			||||||
 | 
					                transform: translateY(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('<style>').text(notificationCSS).appendTo('head');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 卡片淡入效果
 | 
				
			||||||
 | 
					    $('.book-card').each(function(index) {
 | 
				
			||||||
 | 
					        $(this).css('animation-delay', `${0.05 * index}s`);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 鼠标悬停时添加卡片提示信息
 | 
				
			||||||
 | 
					    $('.book-card').each(function() {
 | 
				
			||||||
 | 
					        const title = $(this).find('.book-title').text();
 | 
				
			||||||
 | 
					        const author = $(this).find('.book-author').text();
 | 
				
			||||||
 | 
					        $(this).attr('title', `${title} - ${author}`);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 懒加载图片处理(可选)
 | 
				
			||||||
 | 
					    if ('IntersectionObserver' in window) {
 | 
				
			||||||
 | 
					        const imgObserver = new IntersectionObserver((entries, observer) => {
 | 
				
			||||||
 | 
					            entries.forEach(entry => {
 | 
				
			||||||
 | 
					                if (entry.isIntersecting) {
 | 
				
			||||||
 | 
					                    const img = entry.target;
 | 
				
			||||||
 | 
					                    img.src = img.dataset.src;
 | 
				
			||||||
 | 
					                    img.classList.remove('lazy');
 | 
				
			||||||
 | 
					                    observer.unobserve(img);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.querySelectorAll('img.lazy').forEach(img => {
 | 
				
			||||||
 | 
					            imgObserver.observe(img);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // 回退机制,直接加载所有图片
 | 
				
			||||||
 | 
					        document.querySelectorAll('img.lazy').forEach(img => {
 | 
				
			||||||
 | 
					            img.src = img.dataset.src;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										103
									
								
								app/static/js/inventory-adjust.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								app/static/js/inventory-adjust.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					// 库存调整页面的JavaScript功能
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    const changeTypeSelect = document.getElementById('change_type');
 | 
				
			||||||
 | 
					    const changeAmountInput = document.getElementById('change_amount');
 | 
				
			||||||
 | 
					    const stockHint = document.getElementById('stock-hint');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 当前库存数量通过HTML中的全局变量CURRENT_STOCK获取
 | 
				
			||||||
 | 
					    const currentStock = CURRENT_STOCK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 当改变调整类型时,更新提示信息
 | 
				
			||||||
 | 
					    changeTypeSelect.addEventListener('change', updateStockHint);
 | 
				
			||||||
 | 
					    changeAmountInput.addEventListener('input', updateStockHint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function updateStockHint() {
 | 
				
			||||||
 | 
					        const changeType = changeTypeSelect.value;
 | 
				
			||||||
 | 
					        const changeAmount = parseInt(changeAmountInput.value) || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (changeType === 'out') {
 | 
				
			||||||
 | 
					            // 出库检查
 | 
				
			||||||
 | 
					            if (changeAmount > currentStock) {
 | 
				
			||||||
 | 
					                stockHint.textContent = `警告: 出库数量(${changeAmount})超过当前库存(${currentStock})!`;
 | 
				
			||||||
 | 
					                stockHint.className = 'form-text stock-hint danger';
 | 
				
			||||||
 | 
					                changeAmountInput.setCustomValidity('出库数量不能超过当前库存');
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const newStock = currentStock - changeAmount;
 | 
				
			||||||
 | 
					                stockHint.textContent = `出库后库存将变为: ${newStock}`;
 | 
				
			||||||
 | 
					                stockHint.className = 'form-text stock-hint';
 | 
				
			||||||
 | 
					                changeAmountInput.setCustomValidity('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (newStock <= 5 && newStock > 0) {
 | 
				
			||||||
 | 
					                    stockHint.classList.add('warning');
 | 
				
			||||||
 | 
					                } else if (newStock <= 0) {
 | 
				
			||||||
 | 
					                    stockHint.classList.add('danger');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 入库提示
 | 
				
			||||||
 | 
					            const newStock = currentStock + changeAmount;
 | 
				
			||||||
 | 
					            stockHint.textContent = `入库后库存将变为: ${newStock}`;
 | 
				
			||||||
 | 
					            stockHint.className = 'form-text stock-hint';
 | 
				
			||||||
 | 
					            changeAmountInput.setCustomValidity('');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 添加一些迪士尼风格的交互效果
 | 
				
			||||||
 | 
					        addDisneyInteractions();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加迪士尼风格的交互效果
 | 
				
			||||||
 | 
					    function addDisneyInteractions() {
 | 
				
			||||||
 | 
					        // 闪光效果
 | 
				
			||||||
 | 
					        const sparkleEffect = document.querySelector('.disney-sparkles');
 | 
				
			||||||
 | 
					        if (sparkleEffect) {
 | 
				
			||||||
 | 
					            sparkleEffect.style.opacity = '0.7';
 | 
				
			||||||
 | 
					            setTimeout(() => { sparkleEffect.style.opacity = '0'; }, 800);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 按钮动画效果
 | 
				
			||||||
 | 
					        const confirmBtn = document.querySelector('.disney-confirm-btn');
 | 
				
			||||||
 | 
					        if (confirmBtn) {
 | 
				
			||||||
 | 
					            confirmBtn.classList.add('active');
 | 
				
			||||||
 | 
					            setTimeout(() => { confirmBtn.classList.remove('active'); }, 300);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 初始化提示
 | 
				
			||||||
 | 
					    updateStockHint();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 表单提交前验证
 | 
				
			||||||
 | 
					    document.querySelector('form').addEventListener('submit', function(event) {
 | 
				
			||||||
 | 
					        const changeType = changeTypeSelect.value;
 | 
				
			||||||
 | 
					        const changeAmount = parseInt(changeAmountInput.value) || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (changeType === 'out' && changeAmount > currentStock) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            alert('出库数量不能超过当前库存!');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (changeAmount <= 0) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            alert('调整数量必须大于0!');
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 添加提交成功的动画效果
 | 
				
			||||||
 | 
					        const card = document.querySelector('.disney-inventory-card');
 | 
				
			||||||
 | 
					        if (card) {
 | 
				
			||||||
 | 
					            card.classList.add('submitting');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 为表单元素添加迪士尼风格的交互效果
 | 
				
			||||||
 | 
					    const formElements = document.querySelectorAll('.disney-select, .disney-input, .disney-textarea');
 | 
				
			||||||
 | 
					    formElements.forEach(element => {
 | 
				
			||||||
 | 
					        element.addEventListener('focus', function() {
 | 
				
			||||||
 | 
					            this.parentNode.classList.add('focused');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        element.addEventListener('blur', function() {
 | 
				
			||||||
 | 
					            this.parentNode.classList.remove('focused');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										263
									
								
								app/static/js/inventory-book-logs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								app/static/js/inventory-book-logs.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,263 @@
 | 
				
			|||||||
 | 
					// 冰雪奇缘风格的图书库存日志页面JavaScript
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 雪花动画效果
 | 
				
			||||||
 | 
					    createSnowflakes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 表格行动画效果
 | 
				
			||||||
 | 
					    animateTableRows();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 为表格行添加互动效果
 | 
				
			||||||
 | 
					    addTableRowInteractions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加书籍封面魔法效果
 | 
				
			||||||
 | 
					    addBookCoverMagic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加按钮魔法效果
 | 
				
			||||||
 | 
					    addButtonMagic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 响应式表格标签
 | 
				
			||||||
 | 
					    makeTableResponsive();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 创建额外的雪花
 | 
				
			||||||
 | 
					function createSnowflakes() {
 | 
				
			||||||
 | 
					    const snowflakesContainer = document.querySelector('.snowflakes');
 | 
				
			||||||
 | 
					    if (!snowflakesContainer) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加更多雪花,共30个
 | 
				
			||||||
 | 
					    for (let i = 0; i < 20; i++) {
 | 
				
			||||||
 | 
					        const snowflake = document.createElement('div');
 | 
				
			||||||
 | 
					        snowflake.className = 'snowflake';
 | 
				
			||||||
 | 
					        snowflake.textContent = ['❄', '❅', '❆'][Math.floor(Math.random() * 3)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 随机位置和动画
 | 
				
			||||||
 | 
					        const left = Math.random() * 100;
 | 
				
			||||||
 | 
					        const animDuration = 8 + Math.random() * 10;
 | 
				
			||||||
 | 
					        const animDelay = Math.random() * 5;
 | 
				
			||||||
 | 
					        const fontSize = 0.8 + Math.random() * 1.2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        snowflake.style.left = `${left}%`;
 | 
				
			||||||
 | 
					        snowflake.style.animationDuration = `${animDuration}s`;
 | 
				
			||||||
 | 
					        snowflake.style.animationDelay = `${animDelay}s`;
 | 
				
			||||||
 | 
					        snowflake.style.fontSize = `${fontSize}em`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        snowflakesContainer.appendChild(snowflake);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 表格行动画效果
 | 
				
			||||||
 | 
					function animateTableRows() {
 | 
				
			||||||
 | 
					    const tableRows = document.querySelectorAll('.table-row');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tableRows.forEach((row, index) => {
 | 
				
			||||||
 | 
					        // 设置动画延迟,创建瀑布效果
 | 
				
			||||||
 | 
					        row.style.animationDelay = `${index * 0.08}s`;
 | 
				
			||||||
 | 
					        row.classList.add('fade-in');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 为入库和出库行添加不同的动画效果
 | 
				
			||||||
 | 
					        if (row.dataset.type === 'in') {
 | 
				
			||||||
 | 
					            row.classList.add('in-animation');
 | 
				
			||||||
 | 
					        } else if (row.dataset.type === 'out') {
 | 
				
			||||||
 | 
					            row.classList.add('out-animation');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 为表格行添加互动效果
 | 
				
			||||||
 | 
					function addTableRowInteractions() {
 | 
				
			||||||
 | 
					    const tableRows = document.querySelectorAll('.table-row');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tableRows.forEach(row => {
 | 
				
			||||||
 | 
					        // 点击高亮效果
 | 
				
			||||||
 | 
					        row.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            // 移除其他行的选中状态
 | 
				
			||||||
 | 
					            document.querySelectorAll('.table-row').forEach(r => {
 | 
				
			||||||
 | 
					                r.classList.remove('selected-row');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 添加当前行的选中状态
 | 
				
			||||||
 | 
					            this.classList.add('selected-row');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 添加闪光效果
 | 
				
			||||||
 | 
					            const sparkle = document.createElement('div');
 | 
				
			||||||
 | 
					            sparkle.className = 'row-sparkle';
 | 
				
			||||||
 | 
					            this.appendChild(sparkle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 移除闪光效果
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                sparkle.remove();
 | 
				
			||||||
 | 
					            }, 800);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 鼠标悬停效果
 | 
				
			||||||
 | 
					        row.addEventListener('mouseenter', function() {
 | 
				
			||||||
 | 
					            // 添加冰晶效果
 | 
				
			||||||
 | 
					            const iceEffect = document.createElement('div');
 | 
				
			||||||
 | 
					            iceEffect.className = 'ice-effect';
 | 
				
			||||||
 | 
					            this.appendChild(iceEffect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 移除冰晶效果
 | 
				
			||||||
 | 
					            this.addEventListener('mouseleave', function() {
 | 
				
			||||||
 | 
					                iceEffect.remove();
 | 
				
			||||||
 | 
					            }, { once: true });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 添加书籍封面魔法效果
 | 
				
			||||||
 | 
					function addBookCoverMagic() {
 | 
				
			||||||
 | 
					    const bookCover = document.querySelector('.book-cover');
 | 
				
			||||||
 | 
					    const bookFrame = document.querySelector('.book-frame');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (bookCover && bookFrame) {
 | 
				
			||||||
 | 
					        // 鼠标移动时添加3D效果
 | 
				
			||||||
 | 
					        bookFrame.addEventListener('mousemove', function(e) {
 | 
				
			||||||
 | 
					            const rect = this.getBoundingClientRect();
 | 
				
			||||||
 | 
					            const x = e.clientX - rect.left;
 | 
				
			||||||
 | 
					            const y = e.clientY - rect.top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const centerX = rect.width / 2;
 | 
				
			||||||
 | 
					            const centerY = rect.height / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const deltaX = (x - centerX) / 20;
 | 
				
			||||||
 | 
					            const deltaY = (y - centerY) / 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bookCover.style.transform = `rotate(0deg) perspective(800px) rotateX(${-deltaY}deg) rotateY(${deltaX}deg)`;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 鼠标离开时恢复
 | 
				
			||||||
 | 
					        bookFrame.addEventListener('mouseleave', function() {
 | 
				
			||||||
 | 
					            bookCover.style.transform = 'rotate(3deg)';
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 点击时添加闪光效果
 | 
				
			||||||
 | 
					        bookFrame.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const glow = document.querySelector('.book-glow');
 | 
				
			||||||
 | 
					            glow.style.opacity = '1';
 | 
				
			||||||
 | 
					            glow.style.background = 'radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.8), rgba(173, 216, 230, 0) 70%)';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                glow.style.opacity = '0';
 | 
				
			||||||
 | 
					            }, 500);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 添加按钮魔法效果
 | 
				
			||||||
 | 
					function addButtonMagic() {
 | 
				
			||||||
 | 
					    const buttons = document.querySelectorAll('.frozen-btn');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buttons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('mouseenter', function() {
 | 
				
			||||||
 | 
					            // 创建冰晶效果
 | 
				
			||||||
 | 
					            const sparkles = document.createElement('div');
 | 
				
			||||||
 | 
					            sparkles.className = 'btn-sparkles';
 | 
				
			||||||
 | 
					            this.appendChild(sparkles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 冰晶动画
 | 
				
			||||||
 | 
					            for (let i = 0; i < 5; i++) {
 | 
				
			||||||
 | 
					                const sparkle = document.createElement('div');
 | 
				
			||||||
 | 
					                sparkle.className = 'btn-sparkle';
 | 
				
			||||||
 | 
					                sparkle.style.left = `${Math.random() * 100}%`;
 | 
				
			||||||
 | 
					                sparkle.style.top = `${Math.random() * 100}%`;
 | 
				
			||||||
 | 
					                sparkle.style.animationDelay = `${Math.random() * 0.5}s`;
 | 
				
			||||||
 | 
					                sparkles.appendChild(sparkle);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button.addEventListener('mouseleave', function() {
 | 
				
			||||||
 | 
					            const sparkles = this.querySelector('.btn-sparkles');
 | 
				
			||||||
 | 
					            if (sparkles) {
 | 
				
			||||||
 | 
					                sparkles.remove();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 响应式表格
 | 
				
			||||||
 | 
					function makeTableResponsive() {
 | 
				
			||||||
 | 
					    if (window.innerWidth <= 768) {
 | 
				
			||||||
 | 
					        const headerCells = document.querySelectorAll('.th-frozen');
 | 
				
			||||||
 | 
					        const headers = Array.from(headerCells).map(cell => cell.textContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dataCells = document.querySelectorAll('.td-frozen');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dataCells.forEach((cell, index) => {
 | 
				
			||||||
 | 
					            const headerIndex = index % headers.length;
 | 
				
			||||||
 | 
					            cell.setAttribute('data-label', headers[headerIndex]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 创建额外CSS样式
 | 
				
			||||||
 | 
					function addExtraStyles() {
 | 
				
			||||||
 | 
					    const styleSheet = document.createElement('style');
 | 
				
			||||||
 | 
					    styleSheet.type = 'text/css';
 | 
				
			||||||
 | 
					    styleSheet.innerHTML = `
 | 
				
			||||||
 | 
					        .row-sparkle {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            background: linear-gradient(to right, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1));
 | 
				
			||||||
 | 
					            pointer-events: none;
 | 
				
			||||||
 | 
					            animation: rowSparkle 0.8s ease forwards;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes rowSparkle {
 | 
				
			||||||
 | 
					            0% { transform: translateX(-100%); }
 | 
				
			||||||
 | 
					            100% { transform: translateX(100%); }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .ice-effect {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, rgba(255, 255, 255, 0), rgba(173, 216, 230, 0.1), rgba(255, 255, 255, 0));
 | 
				
			||||||
 | 
					            pointer-events: none;
 | 
				
			||||||
 | 
					            backdrop-filter: brightness(1.03);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .btn-sparkles {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            inset: 0;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					            pointer-events: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .btn-sparkle {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            width: 10px;
 | 
				
			||||||
 | 
					            height: 10px;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            background-color: rgba(255, 255, 255, 0.8);
 | 
				
			||||||
 | 
					            pointer-events: none;
 | 
				
			||||||
 | 
					            animation: btnSparkle 1s ease infinite;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes btnSparkle {
 | 
				
			||||||
 | 
					            0%, 100% { transform: scale(0); opacity: 0; }
 | 
				
			||||||
 | 
					            50% { transform: scale(1); opacity: 1; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .in-animation {
 | 
				
			||||||
 | 
					            border-left: 3px solid rgba(3, 169, 244, 0.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .out-animation {
 | 
				
			||||||
 | 
					            border-left: 3px solid rgba(255, 152, 0, 0.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.head.appendChild(styleSheet);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 初始化额外样式
 | 
				
			||||||
 | 
					addExtraStyles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 窗口大小变化时重新调整
 | 
				
			||||||
 | 
					window.addEventListener('resize', makeTableResponsive);
 | 
				
			||||||
							
								
								
									
										30
									
								
								app/static/js/inventory-list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/static/js/inventory-list.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					// 库存管理页面的JavaScript功能
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 库存显示颜色标记
 | 
				
			||||||
 | 
					    const stockElements = document.querySelectorAll('.book-stock');
 | 
				
			||||||
 | 
					    stockElements.forEach(element => {
 | 
				
			||||||
 | 
					        const stockValue = parseInt(element.textContent.trim());
 | 
				
			||||||
 | 
					        if (stockValue <= 0) {
 | 
				
			||||||
 | 
					            element.classList.add('book-stock-critical');
 | 
				
			||||||
 | 
					        } else if (stockValue <= 5) {
 | 
				
			||||||
 | 
					            element.classList.add('book-stock-warning');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            element.classList.add('book-stock-normal');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 表格排序功能
 | 
				
			||||||
 | 
					    const tableHeaders = document.querySelectorAll('th[data-sort]');
 | 
				
			||||||
 | 
					    tableHeaders.forEach(header => {
 | 
				
			||||||
 | 
					        header.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const sort = this.dataset.sort;
 | 
				
			||||||
 | 
					            const currentOrder = new URLSearchParams(window.location.search).get('order') || 'asc';
 | 
				
			||||||
 | 
					            const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const url = new URL(window.location);
 | 
				
			||||||
 | 
					            url.searchParams.set('sort', sort);
 | 
				
			||||||
 | 
					            url.searchParams.set('order', newOrder);
 | 
				
			||||||
 | 
					            window.location.href = url.toString();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										219
									
								
								app/static/js/inventory-logs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								app/static/js/inventory-logs.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,219 @@
 | 
				
			|||||||
 | 
					// 库存日志页面的JavaScript功能
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 日期选择器联动
 | 
				
			||||||
 | 
					    const dateFrom = document.getElementById('date_from');
 | 
				
			||||||
 | 
					    const dateTo = document.getElementById('date_to');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 确保结束日期不早于开始日期
 | 
				
			||||||
 | 
					    if (dateFrom && dateTo) {
 | 
				
			||||||
 | 
					        dateFrom.addEventListener('change', function() {
 | 
				
			||||||
 | 
					            if (dateTo.value && dateFrom.value > dateTo.value) {
 | 
				
			||||||
 | 
					                dateTo.value = dateFrom.value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dateTo.addEventListener('change', function() {
 | 
				
			||||||
 | 
					            if (dateFrom.value && dateFrom.value > dateTo.value) {
 | 
				
			||||||
 | 
					                dateFrom.value = dateTo.value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 重置筛选按钮
 | 
				
			||||||
 | 
					    const resetButton = document.getElementById('reset-filters');
 | 
				
			||||||
 | 
					    if (resetButton) {
 | 
				
			||||||
 | 
					        resetButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            document.getElementById('book_id').value = '';
 | 
				
			||||||
 | 
					            document.getElementById('change_type').value = '';
 | 
				
			||||||
 | 
					            document.getElementById('date_from').value = '';
 | 
				
			||||||
 | 
					            document.getElementById('date_to').value = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 提交表单以应用重置的筛选条件
 | 
				
			||||||
 | 
					            document.querySelector('form').submit();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 备注信息悬停显示完整内容
 | 
				
			||||||
 | 
					    const remarkCells = document.querySelectorAll('.remark-cell');
 | 
				
			||||||
 | 
					    remarkCells.forEach(cell => {
 | 
				
			||||||
 | 
					        if (cell.offsetWidth < cell.scrollWidth) {
 | 
				
			||||||
 | 
					            cell.title = cell.textContent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 创建雪花效果
 | 
				
			||||||
 | 
					    createSnowflakes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 初始化particles.js特效
 | 
				
			||||||
 | 
					    initParticles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 给表格行添加动画延迟
 | 
				
			||||||
 | 
					    animateTableRows();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 创建雪花效果
 | 
				
			||||||
 | 
					function createSnowflakes() {
 | 
				
			||||||
 | 
					    const snowflakesCount = 50; // 雪花数量
 | 
				
			||||||
 | 
					    const container = document.body;
 | 
				
			||||||
 | 
					    const snowflakeChars = ['❄', '❅', '❆', '✱', '*'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < snowflakesCount; i++) {
 | 
				
			||||||
 | 
					        const snowflake = document.createElement('div');
 | 
				
			||||||
 | 
					        snowflake.className = 'snowflake';
 | 
				
			||||||
 | 
					        snowflake.textContent = snowflakeChars[Math.floor(Math.random() * snowflakeChars.length)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 随机样式
 | 
				
			||||||
 | 
					        snowflake.style.left = `${Math.random() * 100}%`;
 | 
				
			||||||
 | 
					        snowflake.style.opacity = Math.random();
 | 
				
			||||||
 | 
					        snowflake.style.fontSize = `${Math.random() * 15 + 10}px`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 动画
 | 
				
			||||||
 | 
					        const duration = Math.random() * 30 + 20;
 | 
				
			||||||
 | 
					        snowflake.style.animationDuration = `${duration}s`;
 | 
				
			||||||
 | 
					        snowflake.style.animationDelay = `${Math.random() * 5}s`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        container.appendChild(snowflake);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 初始化particles.js
 | 
				
			||||||
 | 
					function initParticles() {
 | 
				
			||||||
 | 
					    if (typeof particlesJS !== 'undefined') {
 | 
				
			||||||
 | 
					        particlesJS('magic-particles', {
 | 
				
			||||||
 | 
					            "particles": {
 | 
				
			||||||
 | 
					                "number": {
 | 
				
			||||||
 | 
					                    "value": 80,
 | 
				
			||||||
 | 
					                    "density": {
 | 
				
			||||||
 | 
					                        "enable": true,
 | 
				
			||||||
 | 
					                        "value_area": 800
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "color": {
 | 
				
			||||||
 | 
					                    "value": ["#a2d5f2", "#6fa8dc", "#cfe2f3", "#b19cd9"]
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "shape": {
 | 
				
			||||||
 | 
					                    "type": ["circle", "star"],
 | 
				
			||||||
 | 
					                    "stroke": {
 | 
				
			||||||
 | 
					                        "width": 0,
 | 
				
			||||||
 | 
					                        "color": "#000000"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "polygon": {
 | 
				
			||||||
 | 
					                        "nb_sides": 5
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "opacity": {
 | 
				
			||||||
 | 
					                    "value": 0.3,
 | 
				
			||||||
 | 
					                    "random": true,
 | 
				
			||||||
 | 
					                    "anim": {
 | 
				
			||||||
 | 
					                        "enable": true,
 | 
				
			||||||
 | 
					                        "speed": 1,
 | 
				
			||||||
 | 
					                        "opacity_min": 0.1,
 | 
				
			||||||
 | 
					                        "sync": false
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "size": {
 | 
				
			||||||
 | 
					                    "value": 5,
 | 
				
			||||||
 | 
					                    "random": true,
 | 
				
			||||||
 | 
					                    "anim": {
 | 
				
			||||||
 | 
					                        "enable": false,
 | 
				
			||||||
 | 
					                        "speed": 40,
 | 
				
			||||||
 | 
					                        "size_min": 0.1,
 | 
				
			||||||
 | 
					                        "sync": false
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "line_linked": {
 | 
				
			||||||
 | 
					                    "enable": true,
 | 
				
			||||||
 | 
					                    "distance": 150,
 | 
				
			||||||
 | 
					                    "color": "#a2d5f2",
 | 
				
			||||||
 | 
					                    "opacity": 0.2,
 | 
				
			||||||
 | 
					                    "width": 1
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "move": {
 | 
				
			||||||
 | 
					                    "enable": true,
 | 
				
			||||||
 | 
					                    "speed": 2,
 | 
				
			||||||
 | 
					                    "direction": "none",
 | 
				
			||||||
 | 
					                    "random": true,
 | 
				
			||||||
 | 
					                    "straight": false,
 | 
				
			||||||
 | 
					                    "out_mode": "out",
 | 
				
			||||||
 | 
					                    "bounce": false,
 | 
				
			||||||
 | 
					                    "attract": {
 | 
				
			||||||
 | 
					                        "enable": true,
 | 
				
			||||||
 | 
					                        "rotateX": 600,
 | 
				
			||||||
 | 
					                        "rotateY": 1200
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "interactivity": {
 | 
				
			||||||
 | 
					                "detect_on": "canvas",
 | 
				
			||||||
 | 
					                "events": {
 | 
				
			||||||
 | 
					                    "onhover": {
 | 
				
			||||||
 | 
					                        "enable": true,
 | 
				
			||||||
 | 
					                        "mode": "grab"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "onclick": {
 | 
				
			||||||
 | 
					                        "enable": true,
 | 
				
			||||||
 | 
					                        "mode": "push"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "resize": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "modes": {
 | 
				
			||||||
 | 
					                    "grab": {
 | 
				
			||||||
 | 
					                        "distance": 140,
 | 
				
			||||||
 | 
					                        "line_linked": {
 | 
				
			||||||
 | 
					                            "opacity": 0.5
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "bubble": {
 | 
				
			||||||
 | 
					                        "distance": 400,
 | 
				
			||||||
 | 
					                        "size": 4,
 | 
				
			||||||
 | 
					                        "duration": 2,
 | 
				
			||||||
 | 
					                        "opacity": 1,
 | 
				
			||||||
 | 
					                        "speed": 3
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "repulse": {
 | 
				
			||||||
 | 
					                        "distance": 200,
 | 
				
			||||||
 | 
					                        "duration": 0.4
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "push": {
 | 
				
			||||||
 | 
					                        "particles_nb": 4
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "remove": {
 | 
				
			||||||
 | 
					                        "particles_nb": 2
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "retina_detect": true
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 给表格行添加动画延迟
 | 
				
			||||||
 | 
					function animateTableRows() {
 | 
				
			||||||
 | 
					    const rows = document.querySelectorAll('.log-row');
 | 
				
			||||||
 | 
					    rows.forEach((row, index) => {
 | 
				
			||||||
 | 
					        row.style.animationDelay = `${index * 0.05}s`;
 | 
				
			||||||
 | 
					        row.style.animationDuration = '0.5s';
 | 
				
			||||||
 | 
					        row.style.animationName = 'fadeInUp';
 | 
				
			||||||
 | 
					        row.style.animationFillMode = 'both';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 添加淡入向上动画
 | 
				
			||||||
 | 
					const fadeInUpKeyframes = `
 | 
				
			||||||
 | 
					@keyframes fadeInUp {
 | 
				
			||||||
 | 
					    from {
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					        transform: translate3d(0, 20px, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    to {
 | 
				
			||||||
 | 
					        opacity: 1;
 | 
				
			||||||
 | 
					        transform: translate3d(0, 0, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 添加动画样式到文档
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    const style = document.createElement('style');
 | 
				
			||||||
 | 
					    style.textContent = fadeInUpKeyframes;
 | 
				
			||||||
 | 
					    document.head.appendChild(style);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										132
									
								
								app/static/js/my_borrows.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								app/static/js/my_borrows.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					// my_borrows.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 归还图书功能
 | 
				
			||||||
 | 
					    const returnButtons = document.querySelectorAll('.return-btn');
 | 
				
			||||||
 | 
					    const returnModal = document.getElementById('returnModal');
 | 
				
			||||||
 | 
					    const returnBookTitle = document.getElementById('returnBookTitle');
 | 
				
			||||||
 | 
					    const confirmReturnButton = document.getElementById('confirmReturn');
 | 
				
			||||||
 | 
					    let currentBorrowId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    returnButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            returnBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmReturnButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送归还请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/return/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 重新加载页面以更新借阅状态
 | 
				
			||||||
 | 
					                setTimeout(() => window.location.reload(), 1500);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 续借图书功能
 | 
				
			||||||
 | 
					    const renewButtons = document.querySelectorAll('.renew-btn');
 | 
				
			||||||
 | 
					    const renewModal = document.getElementById('renewModal');
 | 
				
			||||||
 | 
					    const renewBookTitle = document.getElementById('renewBookTitle');
 | 
				
			||||||
 | 
					    const confirmRenewButton = document.getElementById('confirmRenew');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renewButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            renewBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#renewModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmRenewButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送续借请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/renew/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#renewModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 重新加载页面以更新借阅状态
 | 
				
			||||||
 | 
					                setTimeout(() => window.location.reload(), 1500);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#renewModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 显示提示消息
 | 
				
			||||||
 | 
					    function showAlert(type, message) {
 | 
				
			||||||
 | 
					        const alertDiv = document.createElement('div');
 | 
				
			||||||
 | 
					        alertDiv.className = `alert alert-${type} alert-dismissible fade show fixed-top mx-auto mt-3`;
 | 
				
			||||||
 | 
					        alertDiv.style.maxWidth = '500px';
 | 
				
			||||||
 | 
					        alertDiv.style.zIndex = '9999';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        alertDiv.innerHTML = `
 | 
				
			||||||
 | 
					            ${message}
 | 
				
			||||||
 | 
					            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
 | 
				
			||||||
 | 
					                <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.body.appendChild(alertDiv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 3秒后自动消失
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            alertDiv.remove();
 | 
				
			||||||
 | 
					        }, 3000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										138
									
								
								app/static/js/overdue.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								app/static/js/overdue.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					// overdue.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 归还图书功能
 | 
				
			||||||
 | 
					    const returnButtons = document.querySelectorAll('.return-btn');
 | 
				
			||||||
 | 
					    const returnModal = document.getElementById('returnModal');
 | 
				
			||||||
 | 
					    const returnBookTitle = document.getElementById('returnBookTitle');
 | 
				
			||||||
 | 
					    const overdueRemark = document.getElementById('overdueRemark');
 | 
				
			||||||
 | 
					    const confirmReturnButton = document.getElementById('confirmReturn');
 | 
				
			||||||
 | 
					    let currentBorrowId = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    returnButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            returnBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					            overdueRemark.value = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmReturnButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const remark = overdueRemark.value.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送归还请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/return/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({
 | 
				
			||||||
 | 
					                remark: remark
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 重新加载页面以更新借阅状态
 | 
				
			||||||
 | 
					                setTimeout(() => window.location.reload(), 1500);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#returnModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 逾期通知功能
 | 
				
			||||||
 | 
					    const notifyButtons = document.querySelectorAll('.notify-btn');
 | 
				
			||||||
 | 
					    const notifyModal = document.getElementById('notifyModal');
 | 
				
			||||||
 | 
					    const notifyBookTitle = document.getElementById('notifyBookTitle');
 | 
				
			||||||
 | 
					    const confirmNotifyButton = document.getElementById('confirmNotify');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notifyButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const borrowId = this.getAttribute('data-id');
 | 
				
			||||||
 | 
					            const bookTitle = this.getAttribute('data-title');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            currentBorrowId = borrowId;
 | 
				
			||||||
 | 
					            notifyBookTitle.textContent = bookTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 使用 Bootstrap 的 jQuery 方法显示模态框
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('show');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    confirmNotifyButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        if (!currentBorrowId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送通知请求
 | 
				
			||||||
 | 
					        fetch(`/borrow/overdue/notify/${currentBorrowId}`, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                'X-Requested-With': 'XMLHttpRequest'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({})
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            // 隐藏模态框
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 显示成功消息
 | 
				
			||||||
 | 
					                showAlert('success', data.message);
 | 
				
			||||||
 | 
					                // 禁用已点击的通知按钮
 | 
				
			||||||
 | 
					                document.querySelector(`.notify-btn[data-id="${currentBorrowId}"]`).disabled = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 显示错误消息
 | 
				
			||||||
 | 
					                showAlert('danger', data.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            $('#notifyModal').modal('hide');
 | 
				
			||||||
 | 
					            showAlert('danger', '操作失败,请稍后重试');
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 显示提示消息
 | 
				
			||||||
 | 
					    function showAlert(type, message) {
 | 
				
			||||||
 | 
					        const alertDiv = document.createElement('div');
 | 
				
			||||||
 | 
					        alertDiv.className = `alert alert-${type} alert-dismissible fade show fixed-top mx-auto mt-3`;
 | 
				
			||||||
 | 
					        alertDiv.style.maxWidth = '500px';
 | 
				
			||||||
 | 
					        alertDiv.style.zIndex = '9999';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        alertDiv.innerHTML = `
 | 
				
			||||||
 | 
					            ${message}
 | 
				
			||||||
 | 
					            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
 | 
				
			||||||
 | 
					                <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.body.appendChild(alertDiv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 3秒后自动消失
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            alertDiv.remove();
 | 
				
			||||||
 | 
					        }, 3000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										140
									
								
								app/static/js/user-add.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								app/static/js/user-add.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    // 密码显示/隐藏切换
 | 
				
			||||||
 | 
					    const togglePasswordButtons = document.querySelectorAll('.toggle-password');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    togglePasswordButtons.forEach(button => {
 | 
				
			||||||
 | 
					        button.addEventListener('click', function() {
 | 
				
			||||||
 | 
					            const passwordField = this.previousElementSibling;
 | 
				
			||||||
 | 
					            const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
 | 
				
			||||||
 | 
					            passwordField.setAttribute('type', type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 更改图标
 | 
				
			||||||
 | 
					            const icon = this.querySelector('i');
 | 
				
			||||||
 | 
					            if (type === 'text') {
 | 
				
			||||||
 | 
					                icon.classList.remove('fa-eye');
 | 
				
			||||||
 | 
					                icon.classList.add('fa-eye-slash');
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                icon.classList.remove('fa-eye-slash');
 | 
				
			||||||
 | 
					                icon.classList.add('fa-eye');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 密码一致性检查
 | 
				
			||||||
 | 
					    const passwordInput = document.getElementById('password');
 | 
				
			||||||
 | 
					    const confirmPasswordInput = document.getElementById('confirm_password');
 | 
				
			||||||
 | 
					    const passwordMatchMessage = document.getElementById('password-match-message');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function checkPasswordMatch() {
 | 
				
			||||||
 | 
					        if (confirmPasswordInput.value === '') {
 | 
				
			||||||
 | 
					            passwordMatchMessage.textContent = '';
 | 
				
			||||||
 | 
					            passwordMatchMessage.className = 'form-text';
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (passwordInput.value === confirmPasswordInput.value) {
 | 
				
			||||||
 | 
					            passwordMatchMessage.textContent = '密码匹配';
 | 
				
			||||||
 | 
					            passwordMatchMessage.className = 'form-text text-success';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            passwordMatchMessage.textContent = '密码不匹配';
 | 
				
			||||||
 | 
					            passwordMatchMessage.className = 'form-text text-danger';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    passwordInput.addEventListener('input', checkPasswordMatch);
 | 
				
			||||||
 | 
					    confirmPasswordInput.addEventListener('input', checkPasswordMatch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 发送邮箱验证码
 | 
				
			||||||
 | 
					    const sendVerificationCodeButton = document.getElementById('sendVerificationCode');
 | 
				
			||||||
 | 
					    const emailInput = document.getElementById('email');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendVerificationCodeButton.addEventListener('click', function() {
 | 
				
			||||||
 | 
					        const email = emailInput.value.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 验证邮箱格式
 | 
				
			||||||
 | 
					        if (!email) {
 | 
				
			||||||
 | 
					            alert('请输入邮箱地址');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
				
			||||||
 | 
					        if (!emailRegex.test(email)) {
 | 
				
			||||||
 | 
					            alert('请输入有效的邮箱地址');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 禁用按钮,防止重复点击
 | 
				
			||||||
 | 
					        this.disabled = true;
 | 
				
			||||||
 | 
					        this.textContent = '发送中...';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送AJAX请求
 | 
				
			||||||
 | 
					        fetch('/user/send_verification_code', {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            body: JSON.stringify({ email: email }),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(data => {
 | 
				
			||||||
 | 
					            if (data.success) {
 | 
				
			||||||
 | 
					                // 成功发送,开始倒计时
 | 
				
			||||||
 | 
					                startCountdown(this);
 | 
				
			||||||
 | 
					                alert(data.message);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 发送失败,恢复按钮状态
 | 
				
			||||||
 | 
					                this.disabled = false;
 | 
				
			||||||
 | 
					                this.textContent = '发送验证码';
 | 
				
			||||||
 | 
					                alert(data.message || '发送失败,请稍后重试');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					            this.disabled = false;
 | 
				
			||||||
 | 
					            this.textContent = '发送验证码';
 | 
				
			||||||
 | 
					            alert('发送失败,请稍后重试');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 验证码倒计时(60秒)
 | 
				
			||||||
 | 
					    function startCountdown(button) {
 | 
				
			||||||
 | 
					        let seconds = 60;
 | 
				
			||||||
 | 
					        const originalText = '发送验证码';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const countdownInterval = setInterval(() => {
 | 
				
			||||||
 | 
					            seconds--;
 | 
				
			||||||
 | 
					            button.textContent = `${seconds}秒后重发`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (seconds <= 0) {
 | 
				
			||||||
 | 
					                clearInterval(countdownInterval);
 | 
				
			||||||
 | 
					                button.textContent = originalText;
 | 
				
			||||||
 | 
					                button.disabled = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, 1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 表单提交前验证
 | 
				
			||||||
 | 
					    const addUserForm = document.getElementById('addUserForm');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addUserForm.addEventListener('submit', function(event) {
 | 
				
			||||||
 | 
					        // 检查密码是否匹配
 | 
				
			||||||
 | 
					        if (passwordInput.value !== confirmPasswordInput.value) {
 | 
				
			||||||
 | 
					            event.preventDefault();
 | 
				
			||||||
 | 
					            alert('两次输入的密码不匹配,请重新输入');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 如果还有其他前端验证,可以继续添加
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 自动填充用户名为昵称的默认值
 | 
				
			||||||
 | 
					    const usernameInput = document.getElementById('username');
 | 
				
			||||||
 | 
					    const nicknameInput = document.getElementById('nickname');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usernameInput.addEventListener('change', function() {
 | 
				
			||||||
 | 
					        // 只有当昵称字段为空时才自动填充
 | 
				
			||||||
 | 
					        if (!nicknameInput.value) {
 | 
				
			||||||
 | 
					            nicknameInput.value = this.value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -24,10 +24,10 @@
 | 
				
			|||||||
                    <a href="{{ url_for('index') }}"><i class="fas fa-home"></i> 首页</a>
 | 
					                    <a href="{{ url_for('index') }}"><i class="fas fa-home"></i> 首页</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/book/list' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/book/list' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="{{ url_for('book.book_list') }}"><i class="fas fa-book"></i> 图书浏览</a>
 | 
					                    <a href="{{ url_for('book.browse_books') }}"><i class="fas fa-book"></i> 图书浏览</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/borrow' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/borrow' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="#"><i class="fas fa-bookmark"></i> 我的借阅</a>
 | 
					                    <a href="{{ url_for('borrow.my_borrows') }}"><i class="fas fa-bookmark"></i> 我的借阅</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/announcement' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/announcement' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="#"><i class="fas fa-bell"></i> 通知公告</a>
 | 
					                    <a href="#"><i class="fas fa-bell"></i> 通知公告</a>
 | 
				
			||||||
@ -44,10 +44,12 @@
 | 
				
			|||||||
                    <a href="{{ url_for('book.admin_book_list') }}"><i class="fas fa-layer-group"></i> 图书管理</a>
 | 
					                    <a href="{{ url_for('book.admin_book_list') }}"><i class="fas fa-layer-group"></i> 图书管理</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/borrow/manage' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/borrow/manage' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="#"><i class="fas fa-exchange-alt"></i> 借阅管理</a>
 | 
					                    {% if current_user.role_id == 1 %}
 | 
				
			||||||
 | 
					                    <a href="{{ url_for('borrow.manage_borrows') }}"><i class="fas fa-exchange-alt"></i> 借阅管理</a>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/inventory' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/inventory' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="#"><i class="fas fa-warehouse"></i> 库存管理</a>
 | 
					                    <a href="{{ url_for('inventory.inventory_list') }}"><i class="fas fa-warehouse"></i> 库存管理</a>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
                <li class="{% if '/statistics' in request.path %}active{% endif %}">
 | 
					                <li class="{% if '/statistics' in request.path %}active{% endif %}">
 | 
				
			||||||
                    <a href="#"><i class="fas fa-chart-bar"></i> 统计分析</a>
 | 
					                    <a href="#"><i class="fas fa-chart-bar"></i> 统计分析</a>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										224
									
								
								app/templates/book/browse.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								app/templates/book/browse.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,224 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}图书浏览 - 图书管理系统{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/browse.css') }}">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="browse-container">
 | 
				
			||||||
 | 
					    <!-- 装饰气泡 -->
 | 
				
			||||||
 | 
					    {% for i in range(15) %}
 | 
				
			||||||
 | 
					    <div class="bubble"></div>
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="page-header">
 | 
				
			||||||
 | 
					        <h1>图书浏览</h1>
 | 
				
			||||||
 | 
					        <p class="welcome-text">欢迎 <strong>{{ current_user.nickname or current_user.username }}</strong>,今天想读点什么?</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="filter-section">
 | 
				
			||||||
 | 
					        <form method="GET" action="{{ url_for('book.browse_books') }}" class="search-form">
 | 
				
			||||||
 | 
					            <div class="search-row">
 | 
				
			||||||
 | 
					                <div class="form-group search-group">
 | 
				
			||||||
 | 
					                    <input type="text" name="search" class="form-control" placeholder="搜索书名/作者/ISBN" value="{{ search }}">
 | 
				
			||||||
 | 
					                    <button type="submit" class="btn btn-primary">
 | 
				
			||||||
 | 
					                        <i class="fas fa-search"></i>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="filter-row">
 | 
				
			||||||
 | 
					                <div class="category-filters">
 | 
				
			||||||
 | 
					                    <button type="button" class="category-filter-toggle">
 | 
				
			||||||
 | 
					                        <i class="fas fa-tags"></i> 分类筛选
 | 
				
			||||||
 | 
					                        <i class="fas fa-chevron-down"></i>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    <div class="category-filter-dropdown">
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('book.browse_books', search=search, sort=sort, order=order) }}"
 | 
				
			||||||
 | 
					                           class="category-item {% if not category_id %}active{% endif %}">
 | 
				
			||||||
 | 
					                            <i class="fas fa-globe"></i> 全部
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        {% for category in categories %}
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('book.browse_books', search=search, category_id=category.id, sort=sort, order=order) }}"
 | 
				
			||||||
 | 
					                           class="category-item {% if category_id == category.id %}active{% endif %}">
 | 
				
			||||||
 | 
					                            <i class="fas fa-tag"></i> {{ category.name }}
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="form-group filter-group">
 | 
				
			||||||
 | 
					                    <select name="sort" class="form-control" onchange="this.form.submit()">
 | 
				
			||||||
 | 
					                        <option value="id" {% if sort == 'id' %}selected{% endif %}>默认排序</option>
 | 
				
			||||||
 | 
					                        <option value="created_at" {% if sort == 'created_at' %}selected{% endif %}>入库时间</option>
 | 
				
			||||||
 | 
					                        <option value="title" {% if sort == 'title' %}selected{% endif %}>书名</option>
 | 
				
			||||||
 | 
					                        <option value="stock" {% if sort == 'stock' %}selected{% endif %}>库存</option>
 | 
				
			||||||
 | 
					                    </select>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="form-group filter-group">
 | 
				
			||||||
 | 
					                    <select name="order" class="form-control" onchange="this.form.submit()">
 | 
				
			||||||
 | 
					                        <option value="desc" {% if order == 'desc' %}selected{% endif %}>降序</option>
 | 
				
			||||||
 | 
					                        <option value="asc" {% if order == 'asc' %}selected{% endif %}>升序</option>
 | 
				
			||||||
 | 
					                    </select>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- 图书数据统计 -->
 | 
				
			||||||
 | 
					    <div class="browse-stats">
 | 
				
			||||||
 | 
					        <div class="stat-item">
 | 
				
			||||||
 | 
					            <i class="fas fa-book"></i>
 | 
				
			||||||
 | 
					            <div class="stat-content">
 | 
				
			||||||
 | 
					                <span class="stat-value">{{ pagination.total }}</span>
 | 
				
			||||||
 | 
					                <span class="stat-label">可借图书</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="stat-item">
 | 
				
			||||||
 | 
					            <i class="fas fa-tag"></i>
 | 
				
			||||||
 | 
					            <div class="stat-content">
 | 
				
			||||||
 | 
					                <span class="stat-value">{{ categories|length }}</span>
 | 
				
			||||||
 | 
					                <span class="stat-label">图书分类</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% if search %}
 | 
				
			||||||
 | 
					        <div class="search-results">
 | 
				
			||||||
 | 
					            搜索 "{{ search }}" 找到 {{ pagination.total }} 本图书
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="books-grid">
 | 
				
			||||||
 | 
					        {% for book in books %}
 | 
				
			||||||
 | 
					        <div class="book-card" data-id="{{ book.id }}">
 | 
				
			||||||
 | 
					            <div class="book-cover">
 | 
				
			||||||
 | 
					                {% if book.cover_url %}
 | 
				
			||||||
 | 
					                <img src="{{ book.cover_url }}" alt="{{ book.title }}">
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                <div class="no-cover">
 | 
				
			||||||
 | 
					                    <i class="fas fa-book"></i>
 | 
				
			||||||
 | 
					                    <span>无封面</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                <div class="cover-overlay"></div>
 | 
				
			||||||
 | 
					                <div class="book-ribbon">
 | 
				
			||||||
 | 
					                    {% if book.stock > 0 %}
 | 
				
			||||||
 | 
					                    <span class="available">可借阅</span>
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <span class="unavailable">无库存</span>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="book-info">
 | 
				
			||||||
 | 
					                <h3 class="book-title" title="{{ book.title }}">{{ book.title }}</h3>
 | 
				
			||||||
 | 
					                <div class="book-author">{{ book.author }}</div>
 | 
				
			||||||
 | 
					                <div class="book-meta">
 | 
				
			||||||
 | 
					                    {% if book.category %}
 | 
				
			||||||
 | 
					                    <span class="book-category">{{ book.category.name }}</span>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    <span class="book-year">{{ book.publish_year or '未知年份' }}</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="book-actions">
 | 
				
			||||||
 | 
					                    <a href="{{ url_for('book.book_detail', book_id=book.id) }}" class="btn-detail">
 | 
				
			||||||
 | 
					                        <i class="fas fa-info-circle"></i> 详情
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    {% if book.stock > 0 %}
 | 
				
			||||||
 | 
					                    <a href="#" class="btn-borrow borrow-book" data-id="{{ book.id }}">
 | 
				
			||||||
 | 
					                        <i class="fas fa-hand-holding"></i> 借阅
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <button class="btn-borrow disabled" disabled>
 | 
				
			||||||
 | 
					                        <i class="fas fa-hand-holding"></i> 暂无库存
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					        <div class="no-books">
 | 
				
			||||||
 | 
					            <img src="{{ url_for('static', filename='images/no-books.svg') }}" alt="没有找到书籍" class="no-books-img">
 | 
				
			||||||
 | 
					            <h3>没有找到符合条件的图书</h3>
 | 
				
			||||||
 | 
					            <p>尝试调整搜索条件或浏览其他分类</p>
 | 
				
			||||||
 | 
					            <a href="{{ url_for('book.browse_books') }}" class="btn-reset-search">
 | 
				
			||||||
 | 
					                <i class="fas fa-sync"></i> 重置搜索
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        {% endfor %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- 分页 -->
 | 
				
			||||||
 | 
					    {% if pagination.pages > 1 %}
 | 
				
			||||||
 | 
					    <div class="pagination-container">
 | 
				
			||||||
 | 
					        <ul class="pagination">
 | 
				
			||||||
 | 
					            {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					            <li class="page-item">
 | 
				
			||||||
 | 
					                <a class="page-link" href="{{ url_for('book.browse_books', page=pagination.prev_num, search=search, category_id=category_id, sort=sort, order=order) }}">
 | 
				
			||||||
 | 
					                    <i class="fas fa-chevron-left"></i>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {% for p in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
 | 
				
			||||||
 | 
					                {% if p %}
 | 
				
			||||||
 | 
					                    {% if p == pagination.page %}
 | 
				
			||||||
 | 
					                    <li class="page-item active">
 | 
				
			||||||
 | 
					                        <span class="page-link">{{ p }}</span>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <li class="page-item">
 | 
				
			||||||
 | 
					                        <a class="page-link" href="{{ url_for('book.browse_books', page=p, search=search, category_id=category_id, sort=sort, order=order) }}">{{ p }}</a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                <li class="page-item disabled">
 | 
				
			||||||
 | 
					                    <span class="page-link">...</span>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {% if pagination.has_next %}
 | 
				
			||||||
 | 
					            <li class="page-item">
 | 
				
			||||||
 | 
					                <a class="page-link" href="{{ url_for('book.browse_books', page=pagination.next_num, search=search, category_id=category_id, sort=sort, order=order) }}">
 | 
				
			||||||
 | 
					                    <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					        <div class="pagination-info">
 | 
				
			||||||
 | 
					            显示 {{ pagination.total }} 条结果中的第 {{ (pagination.page - 1) * pagination.per_page + 1 }}
 | 
				
			||||||
 | 
					            到 {{ min(pagination.page * pagination.per_page, pagination.total) }} 条
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 借阅确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="borrowModal" tabindex="-1" role="dialog" aria-labelledby="borrowModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					  <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					    <div class="modal-content">
 | 
				
			||||||
 | 
					      <div class="modal-header">
 | 
				
			||||||
 | 
					        <h5 class="modal-title" id="borrowModalLabel">确认借阅</h5>
 | 
				
			||||||
 | 
					        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					          <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="modal-body">
 | 
				
			||||||
 | 
					        <p>确定要借阅《<span id="borrowBookTitle"></span>》吗?</p>
 | 
				
			||||||
 | 
					        <p class="modal-info">借阅期限为30天,请在到期前归还。</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="modal-footer">
 | 
				
			||||||
 | 
					        <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					        <button type="button" class="btn btn-primary" id="confirmBorrow">确认借阅</button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/browse.js') }}"></script>
 | 
				
			||||||
 | 
					{{ super() }}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@ -11,18 +11,33 @@
 | 
				
			|||||||
    <div class="page-header">
 | 
					    <div class="page-header">
 | 
				
			||||||
        <h1>图书详情</h1>
 | 
					        <h1>图书详情</h1>
 | 
				
			||||||
        <div class="actions">
 | 
					        <div class="actions">
 | 
				
			||||||
 | 
					            <!-- 根据来源返回不同页面 -->
 | 
				
			||||||
 | 
					            {% if request.referrer and 'browse' in request.referrer %}
 | 
				
			||||||
 | 
					            <a href="{{ url_for('book.browse_books') }}" class="btn btn-secondary">
 | 
				
			||||||
 | 
					                <i class="fas fa-arrow-left"></i> 返回浏览
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            {% else %}
 | 
				
			||||||
            <a href="{{ url_for('book.book_list') }}" class="btn btn-secondary">
 | 
					            <a href="{{ url_for('book.book_list') }}" class="btn btn-secondary">
 | 
				
			||||||
                <i class="fas fa-arrow-left"></i> 返回列表
 | 
					                <i class="fas fa-arrow-left"></i> 返回列表
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
            {% if current_user.role_id == 1 %}
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 编辑按钮只对管理员显示 -->
 | 
				
			||||||
 | 
					            {% if current_user.is_authenticated and current_user.role_id == 1 %}
 | 
				
			||||||
            <a href="{{ url_for('book.edit_book', book_id=book.id) }}" class="btn btn-primary">
 | 
					            <a href="{{ url_for('book.edit_book', book_id=book.id) }}" class="btn btn-primary">
 | 
				
			||||||
                <i class="fas fa-edit"></i> 编辑图书
 | 
					                <i class="fas fa-edit"></i> 编辑图书
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 借阅按钮对所有用户显示,但需要有库存 -->
 | 
				
			||||||
            {% if book.stock > 0 %}
 | 
					            {% if book.stock > 0 %}
 | 
				
			||||||
            <a href="#" class="btn btn-success" id="borrowBtn">
 | 
					            <a href="#" class="btn btn-success" id="borrowBtn">
 | 
				
			||||||
                <i class="fas fa-hand-holding"></i> 借阅此书
 | 
					                <i class="fas fa-hand-holding"></i> 借阅此书
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
 | 
					            {% else %}
 | 
				
			||||||
 | 
					            <button class="btn btn-secondary" disabled>
 | 
				
			||||||
 | 
					                <i class="fas fa-ban"></i> 暂无库存
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -106,7 +121,7 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- 借阅历史 (仅管理员可见) -->
 | 
					        <!-- 借阅历史 (仅管理员可见) -->
 | 
				
			||||||
        {% if current_user.role_id == 1 %}
 | 
					        {% if current_user.is_authenticated and current_user.role_id == 1 %}
 | 
				
			||||||
        <div class="book-borrow-history">
 | 
					        <div class="book-borrow-history">
 | 
				
			||||||
            <h3>借阅历史</h3>
 | 
					            <h3>借阅历史</h3>
 | 
				
			||||||
            {% if borrow_records %}
 | 
					            {% if borrow_records %}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,15 +3,32 @@
 | 
				
			|||||||
{% block title %}编辑图书 - {{ book.title }}{% endblock %}
 | 
					{% block title %}编辑图书 - {{ book.title }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block head %}
 | 
					{% block head %}
 | 
				
			||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/book-form.css') }}">
 | 
					  <!-- 使用我们为成熟御姐风新建的 CSS -->
 | 
				
			||||||
 | 
					  <link rel="stylesheet" href="{{ url_for('static', filename='css/book-edit.css') }}">
 | 
				
			||||||
 | 
					  <!-- 字体 -->
 | 
				
			||||||
 | 
					  <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<div class="book-form-container">
 | 
					<div class="book-form-container">
 | 
				
			||||||
 | 
					  <!-- 显示Flash消息 -->
 | 
				
			||||||
 | 
					  {% with messages = get_flashed_messages(with_categories=true) %}
 | 
				
			||||||
 | 
					    {% if messages %}
 | 
				
			||||||
 | 
					      {% for category, message in messages %}
 | 
				
			||||||
 | 
					        <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
 | 
				
			||||||
 | 
					          {{ message }}
 | 
				
			||||||
 | 
					          <button type="button" class="close" data-dismiss="alert" aria-label="Close">
 | 
				
			||||||
 | 
					            <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      {% endfor %}
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					  {% endwith %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="page-header">
 | 
					  <div class="page-header">
 | 
				
			||||||
    <h1>编辑图书</h1>
 | 
					    <h1>编辑图书</h1>
 | 
				
			||||||
    <div class="actions">
 | 
					    <div class="actions">
 | 
				
			||||||
            <a href="{{ url_for('book.book_detail', book_id=book.id) }}" class="btn btn-info">
 | 
					      <a href="{{ url_for('book.book_detail', book_id=book.id) }}" class="btn btn-secondary">
 | 
				
			||||||
        <i class="fas fa-eye"></i> 查看详情
 | 
					        <i class="fas fa-eye"></i> 查看详情
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <a href="{{ url_for('book.book_list') }}" class="btn btn-secondary">
 | 
					      <a href="{{ url_for('book.book_list') }}" class="btn btn-secondary">
 | 
				
			||||||
@ -26,12 +43,11 @@
 | 
				
			|||||||
        <div class="card">
 | 
					        <div class="card">
 | 
				
			||||||
          <div class="card-header">基本信息</div>
 | 
					          <div class="card-header">基本信息</div>
 | 
				
			||||||
          <div class="card-body">
 | 
					          <div class="card-body">
 | 
				
			||||||
                        <div class="form-row">
 | 
					
 | 
				
			||||||
                            <div class="form-group col-md-12">
 | 
					            <div class="form-group">
 | 
				
			||||||
              <label for="title">书名 <span class="required">*</span></label>
 | 
					              <label for="title">书名 <span class="required">*</span></label>
 | 
				
			||||||
              <input type="text" class="form-control" id="title" name="title" value="{{ book.title }}" required>
 | 
					              <input type="text" class="form-control" id="title" name="title" value="{{ book.title }}" required>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div class="form-row">
 | 
					            <div class="form-row">
 | 
				
			||||||
              <div class="form-group col-md-6">
 | 
					              <div class="form-group col-md-6">
 | 
				
			||||||
@ -47,7 +63,14 @@
 | 
				
			|||||||
            <div class="form-row">
 | 
					            <div class="form-row">
 | 
				
			||||||
              <div class="form-group col-md-6">
 | 
					              <div class="form-group col-md-6">
 | 
				
			||||||
                <label for="isbn">ISBN</label>
 | 
					                <label for="isbn">ISBN</label>
 | 
				
			||||||
                                <input type="text" class="form-control" id="isbn" name="isbn" value="{{ book.isbn or '' }}">
 | 
					                <input type="text" class="form-control {% if isbn_error %}is-invalid{% endif %}"
 | 
				
			||||||
 | 
					                       id="isbn" name="isbn" value="{{ book.isbn or '' }}">
 | 
				
			||||||
 | 
					                {% if isbn_error %}
 | 
				
			||||||
 | 
					                <div class="invalid-feedback">
 | 
				
			||||||
 | 
					                  {{ isbn_error }}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                <small class="form-text text-muted">ISBN必须是有效的10位或13位格式</small>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div class="form-group col-md-6">
 | 
					              <div class="form-group col-md-6">
 | 
				
			||||||
                <label for="publish_year">出版年份</label>
 | 
					                <label for="publish_year">出版年份</label>
 | 
				
			||||||
@ -59,7 +82,7 @@
 | 
				
			|||||||
              <div class="form-group col-md-6">
 | 
					              <div class="form-group col-md-6">
 | 
				
			||||||
                <label for="category_id">分类</label>
 | 
					                <label for="category_id">分类</label>
 | 
				
			||||||
                <select class="form-control" id="category_id" name="category_id">
 | 
					                <select class="form-control" id="category_id" name="category_id">
 | 
				
			||||||
                                    <option value="">未分类</option>
 | 
					                  <option value="">请选择分类</option>
 | 
				
			||||||
                  {% for category in categories %}
 | 
					                  {% for category in categories %}
 | 
				
			||||||
                    <option value="{{ category.id }}" {% if book.category_id == category.id %}selected{% endif %}>
 | 
					                    <option value="{{ category.id }}" {% if book.category_id == category.id %}selected{% endif %}>
 | 
				
			||||||
                      {{ category.name }}
 | 
					                      {{ category.name }}
 | 
				
			||||||
@ -72,14 +95,15 @@
 | 
				
			|||||||
                <input type="text" class="form-control" id="tags" name="tags" value="{{ book.tags or '' }}" placeholder="多个标签用逗号分隔">
 | 
					                <input type="text" class="form-control" id="tags" name="tags" value="{{ book.tags or '' }}" placeholder="多个标签用逗号分隔">
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="card mt-4">
 | 
					        <div class="card">
 | 
				
			||||||
          <div class="card-header">图书简介</div>
 | 
					          <div class="card-header">图书简介</div>
 | 
				
			||||||
          <div class="card-body">
 | 
					          <div class="card-body">
 | 
				
			||||||
            <div class="form-group">
 | 
					            <div class="form-group">
 | 
				
			||||||
                            <textarea class="form-control" id="description" name="description" rows="8" placeholder="请输入图书简介">{{ book.description or '' }}</textarea>
 | 
					              <textarea class="form-control" id="description" name="description" rows="8" placeholder="请输入图书简介...">{{ book.description or '' }}</textarea>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@ -89,43 +113,36 @@
 | 
				
			|||||||
        <div class="card">
 | 
					        <div class="card">
 | 
				
			||||||
          <div class="card-header">封面图片</div>
 | 
					          <div class="card-header">封面图片</div>
 | 
				
			||||||
          <div class="card-body">
 | 
					          <div class="card-body">
 | 
				
			||||||
                        <div class="cover-preview-container">
 | 
					 | 
				
			||||||
            <div class="cover-preview" id="coverPreview">
 | 
					            <div class="cover-preview" id="coverPreview">
 | 
				
			||||||
              {% if book.cover_url %}
 | 
					              {% if book.cover_url %}
 | 
				
			||||||
                <img src="{{ book.cover_url }}" class="cover-image" alt="{{ book.title }}">
 | 
					                <img src="{{ book.cover_url }}" class="cover-image" alt="{{ book.title }}">
 | 
				
			||||||
              {% else %}
 | 
					              {% else %}
 | 
				
			||||||
                <div class="no-cover-placeholder">
 | 
					                <div class="no-cover-placeholder">
 | 
				
			||||||
                                    <i class="fas fa-image"></i>
 | 
					                  <i class="fas fa-book"></i>
 | 
				
			||||||
                  <span>暂无封面</span>
 | 
					                  <span>暂无封面</span>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              {% endif %}
 | 
					              {% endif %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
                            <div class="upload-container">
 | 
					            <div class="upload-container" style="margin-top: 1rem;">
 | 
				
			||||||
                                <label for="cover" class="btn btn-outline-primary btn-block">
 | 
					              <label for="cover" class="btn btn-secondary btn-block">
 | 
				
			||||||
                                    <i class="fas fa-upload"></i> 更换封面
 | 
					                更换封面
 | 
				
			||||||
              </label>
 | 
					              </label>
 | 
				
			||||||
              <input type="file" id="cover" name="cover" class="form-control-file" accept="image/*" style="display:none;">
 | 
					              <input type="file" id="cover" name="cover" class="form-control-file" accept="image/*" style="display:none;">
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="card mt-4">
 | 
					        <div class="card">
 | 
				
			||||||
                    <div class="card-header">库存和价格</div>
 | 
					          <div class="card-header">库存与价格</div>
 | 
				
			||||||
          <div class="card-body">
 | 
					          <div class="card-body">
 | 
				
			||||||
            <div class="form-group">
 | 
					            <div class="form-group">
 | 
				
			||||||
              <label for="stock">库存数量</label>
 | 
					              <label for="stock">库存数量</label>
 | 
				
			||||||
              <input type="number" class="form-control" id="stock" name="stock" min="0" value="{{ book.stock }}">
 | 
					              <input type="number" class="form-control" id="stock" name="stock" min="0" value="{{ book.stock }}">
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="form-group">
 | 
					            <div class="form-group">
 | 
				
			||||||
                            <label for="price">价格</label>
 | 
					              <label for="price">价格(¥)</label>
 | 
				
			||||||
                            <div class="input-group">
 | 
					 | 
				
			||||||
                                <div class="input-group-prepend">
 | 
					 | 
				
			||||||
                                    <span class="input-group-text">¥</span>
 | 
					 | 
				
			||||||
                                </div>
 | 
					 | 
				
			||||||
              <input type="number" class="form-control" id="price" name="price" step="0.01" min="0" value="{{ book.price or '' }}">
 | 
					              <input type="number" class="form-control" id="price" name="price" step="0.01" min="0" value="{{ book.price or '' }}">
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
            <div class="form-group">
 | 
					            <div class="form-group">
 | 
				
			||||||
              <label for="status">状态</label>
 | 
					              <label for="status">状态</label>
 | 
				
			||||||
              <select class="form-control" id="status" name="status">
 | 
					              <select class="form-control" id="status" name="status">
 | 
				
			||||||
@ -138,7 +155,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <div class="form-submit-container">
 | 
					        <div class="form-submit-container">
 | 
				
			||||||
          <button type="submit" class="btn btn-primary btn-lg btn-block">
 | 
					          <button type="submit" class="btn btn-primary btn-lg btn-block">
 | 
				
			||||||
                        <i class="fas fa-save"></i> 保存修改
 | 
					            保存修改
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -149,29 +166,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{% block scripts %}
 | 
					{% block scripts %}
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    $(document).ready(function() {
 | 
					  // 安全地传递变量给JS
 | 
				
			||||||
        // 封面预览
 | 
					  const bookCoverUrl = {{ book.cover_url|default('', true)|tojson|safe }};
 | 
				
			||||||
        $('#cover').change(function() {
 | 
					  const bookTitle = {{ book.title|default('', true)|tojson|safe }};
 | 
				
			||||||
            const file = this.files[0];
 | 
					  const bookId = {{ book.id|default(0)|tojson|safe }};
 | 
				
			||||||
            if (file) {
 | 
					 | 
				
			||||||
                const reader = new FileReader();
 | 
					 | 
				
			||||||
                reader.onload = function(e) {
 | 
					 | 
				
			||||||
                    $('#coverPreview').html(`<img src="${e.target.result}" class="cover-image">`);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                reader.readAsDataURL(file);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                $('#coverPreview').html(`
 | 
					 | 
				
			||||||
                    {% if book.cover_url %}
 | 
					 | 
				
			||||||
                    <img src="{{ book.cover_url }}" class="cover-image" alt="{{ book.title }}">
 | 
					 | 
				
			||||||
                    {% else %}
 | 
					 | 
				
			||||||
                    <div class="no-cover-placeholder">
 | 
					 | 
				
			||||||
                        <i class="fas fa-image"></i>
 | 
					 | 
				
			||||||
                        <span>暂无封面</span>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    {% endif %}
 | 
					 | 
				
			||||||
                `);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					<!-- 确保jQuery已加载 -->
 | 
				
			||||||
 | 
					<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 | 
				
			||||||
 | 
					<!-- 加载验证脚本 -->
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/book-edit.js') }}"></script>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										317
									
								
								app/templates/borrow/borrow_management.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								app/templates/borrow/borrow_management.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,317 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}借阅管理 - 图书管理系统{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/borrow_management.css') }}">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container">
 | 
				
			||||||
 | 
					    <div class="decorative-corner"></div>
 | 
				
			||||||
 | 
					    <h1 class="page-title">借阅管理</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-md-8">
 | 
				
			||||||
 | 
					            <div class="tabs">
 | 
				
			||||||
 | 
					                <a href="{{ url_for('borrow.manage_borrows', status=1, search=search, user_id=user_id, book_id=book_id) }}" class="tab {% if status == 1 %}active{% endif %}">
 | 
				
			||||||
 | 
					                    当前借阅 <span class="count">{{ current_borrows_count }}</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                <a href="{{ url_for('borrow.manage_borrows', status=0, search=search, user_id=user_id, book_id=book_id) }}" class="tab {% if status == 0 %}active{% endif %}">
 | 
				
			||||||
 | 
					                    已归还 <span class="count">{{ history_borrows_count }}</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                <a href="{{ url_for('borrow.manage_borrows', search=search, user_id=user_id, book_id=book_id) }}" class="tab {% if status is none %}active{% endif %}">
 | 
				
			||||||
 | 
					                    全部借阅 <span class="count">{{ current_borrows_count + history_borrows_count }}</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                <a href="{{ url_for('borrow.overdue_borrows') }}" class="tab overdue-tab">
 | 
				
			||||||
 | 
					                    逾期管理 <span class="count overdue-count">{{ overdue_count }}</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-md-4">
 | 
				
			||||||
 | 
					            <div class="float-right">
 | 
				
			||||||
 | 
					                <button class="btn btn-primary" data-toggle="modal" data-target="#addBorrowModal">
 | 
				
			||||||
 | 
					                    <i class="fas fa-plus"></i> 添加借阅
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="card search-card">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					            <form action="{{ url_for('borrow.manage_borrows') }}" method="GET" class="search-form">
 | 
				
			||||||
 | 
					                {% if status is not none %}<input type="hidden" name="status" value="{{ status }}">{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="row">
 | 
				
			||||||
 | 
					                    <div class="col-md-6">
 | 
				
			||||||
 | 
					                        <div class="input-group">
 | 
				
			||||||
 | 
					                            <input type="text" class="form-control" name="search" value="{{ search }}" placeholder="搜索用户名或图书标题...">
 | 
				
			||||||
 | 
					                            <div class="input-group-append">
 | 
				
			||||||
 | 
					                                <button class="btn btn-outline-secondary" type="submit">
 | 
				
			||||||
 | 
					                                    <i class="fas fa-search"></i>
 | 
				
			||||||
 | 
					                                </button>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-md-3">
 | 
				
			||||||
 | 
					                        <select class="form-control" name="user_id" onchange="this.form.submit()">
 | 
				
			||||||
 | 
					                            <option value="">选择用户</option>
 | 
				
			||||||
 | 
					                            {% for user in users %}
 | 
				
			||||||
 | 
					                                <option value="{{ user.id }}" {% if user_id == user.id %}selected{% endif %}>
 | 
				
			||||||
 | 
					                                    {{ user.username }}
 | 
				
			||||||
 | 
					                                </option>
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
 | 
					                        </select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    {% if search or user_id or book_id %}
 | 
				
			||||||
 | 
					                    <div class="col-md-3">
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('borrow.manage_borrows', status=status) }}" class="btn btn-outline-secondary clear-filters">
 | 
				
			||||||
 | 
					                            <i class="fas fa-times"></i> 清除筛选
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="borrow-list">
 | 
				
			||||||
 | 
					        {% if pagination.items %}
 | 
				
			||||||
 | 
					            <table class="borrow-table">
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th width="10%">图书封面</th>
 | 
				
			||||||
 | 
					                        <th width="20%">书名</th>
 | 
				
			||||||
 | 
					                        <th width="15%">借阅用户</th>
 | 
				
			||||||
 | 
					                        <th width="12%">借阅日期</th>
 | 
				
			||||||
 | 
					                        <th width="12%">应还日期</th>
 | 
				
			||||||
 | 
					                        <th width="15%">状态</th>
 | 
				
			||||||
 | 
					                        <th width="16%">操作</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    {% for borrow in pagination.items %}
 | 
				
			||||||
 | 
					                    <tr class="borrow-item {% if borrow.status == 1 and borrow.due_date < now %}overdue{% endif %}">
 | 
				
			||||||
 | 
					                        <td class="book-cover">
 | 
				
			||||||
 | 
					                            {% if borrow.book.cover_url %}
 | 
				
			||||||
 | 
					                                <img src="{{ borrow.book.cover_url }}" alt="{{ borrow.book.title }}">
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <img src="{{ url_for('static', filename='images/book-placeholder.jpg') }}" alt="{{ borrow.book.title }}">
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="book-title">
 | 
				
			||||||
 | 
					                            <a href="{{ url_for('book.book_detail', book_id=borrow.book_id) }}">{{ borrow.book.title }}</a>
 | 
				
			||||||
 | 
					                            <div class="book-author">{{ borrow.book.author }}</div>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="user-info">
 | 
				
			||||||
 | 
					                            <a href="{{ url_for('user.user_edit', user_id=borrow.user_id) }}">{{ borrow.user.username }}</a>
 | 
				
			||||||
 | 
					                            {% if borrow.user.nickname %}
 | 
				
			||||||
 | 
					                                <div class="user-nickname">{{ borrow.user.nickname }}</div>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>{{ borrow.borrow_date.strftime('%Y-%m-%d') }}</td>
 | 
				
			||||||
 | 
					                        <td class="due-date {% if borrow.status == 1 and borrow.due_date < now %}text-danger{% endif %}">
 | 
				
			||||||
 | 
					                            {{ borrow.due_date.strftime('%Y-%m-%d') }}
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 and borrow.due_date < now %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-danger">已逾期</span>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-primary">借阅中</span>
 | 
				
			||||||
 | 
					                                {% if borrow.renew_count > 0 %}
 | 
				
			||||||
 | 
					                                    <span class="badge badge-info">已续借{{ borrow.renew_count }}次</span>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-success">已归还</span>
 | 
				
			||||||
 | 
					                                <div class="return-date">{{ borrow.return_date.strftime('%Y-%m-%d') }}</div>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="actions">
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 %}
 | 
				
			||||||
 | 
					                                <button class="btn btn-sm btn-success return-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">归还</button>
 | 
				
			||||||
 | 
					                                {% if borrow.renew_count < 2 and borrow.due_date >= now %}
 | 
				
			||||||
 | 
					                                    <button class="btn btn-sm btn-primary renew-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">续借</button>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                                {% if borrow.due_date < now %}
 | 
				
			||||||
 | 
					                                    <button class="btn btn-sm btn-warning notify-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">逾期通知</button>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 分页 -->
 | 
				
			||||||
 | 
					            <div class="pagination-container">
 | 
				
			||||||
 | 
					                {% if pagination.pages > 1 %}
 | 
				
			||||||
 | 
					                <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					                    <ul class="pagination">
 | 
				
			||||||
 | 
					                        {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.manage_borrows', page=pagination.prev_num, status=status, search=search, user_id=user_id, book_id=book_id) }}" aria-label="Previous">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true">«</span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                            {% if page_num %}
 | 
				
			||||||
 | 
					                                <li class="page-item {% if page_num == pagination.page %}active{% endif %}">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="{{ url_for('borrow.manage_borrows', page=page_num, status=status, search=search, user_id=user_id, book_id=book_id) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <li class="page-item disabled"><span class="page-link">...</span></li>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.manage_borrows', page=pagination.next_num, status=status, search=search, user_id=user_id, book_id=book_id) }}" aria-label="Next">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true">»</span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </nav>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					            <div class="no-records">
 | 
				
			||||||
 | 
					                <i class="fas fa-book-reader empty-icon"></i>
 | 
				
			||||||
 | 
					                <p class="empty-text">
 | 
				
			||||||
 | 
					                    {% if status == 1 %}
 | 
				
			||||||
 | 
					                        没有进行中的借阅记录。
 | 
				
			||||||
 | 
					                    {% elif status == 0 %}
 | 
				
			||||||
 | 
					                        没有已归还的借阅记录。
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                        没有任何借阅记录。
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 添加借阅模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="addBorrowModal" tabindex="-1" role="dialog" aria-labelledby="addBorrowModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="addBorrowModalLabel">添加借阅记录</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <form action="{{ url_for('borrow.admin_add_borrow') }}" method="POST">
 | 
				
			||||||
 | 
					                <div class="modal-body">
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <label for="userSelect">借阅用户</label>
 | 
				
			||||||
 | 
					                        <select class="form-control" id="userSelect" name="user_id" required>
 | 
				
			||||||
 | 
					                            <option value="">请选择用户</option>
 | 
				
			||||||
 | 
					                            {% for user in users %}
 | 
				
			||||||
 | 
					                                <option value="{{ user.id }}">{{ user.username }}{% if user.nickname %} ({{ user.nickname }}){% endif %}</option>
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
 | 
					                        </select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <label for="bookSelect">借阅图书</label>
 | 
				
			||||||
 | 
					                        <div class="input-group">
 | 
				
			||||||
 | 
					                            <input type="text" class="form-control" id="bookSearch" placeholder="搜索图书...">
 | 
				
			||||||
 | 
					                            <div class="input-group-append">
 | 
				
			||||||
 | 
					                                <button class="btn btn-outline-secondary" type="button" id="searchBookBtn">
 | 
				
			||||||
 | 
					                                    <i class="fas fa-search"></i>
 | 
				
			||||||
 | 
					                                </button>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <select class="form-control mt-2" id="bookSelect" name="book_id" required>
 | 
				
			||||||
 | 
					                            <option value="">请先搜索图书</option>
 | 
				
			||||||
 | 
					                        </select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <label for="borrowDays">借阅天数</label>
 | 
				
			||||||
 | 
					                        <input type="number" class="form-control" id="borrowDays" name="borrow_days" value="14" min="1" max="60">
 | 
				
			||||||
 | 
					                        <small class="form-text text-muted">默认借阅时间为14天</small>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="modal-footer">
 | 
				
			||||||
 | 
					                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                    <button type="submit" class="btn btn-primary">添加借阅</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 归还确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="returnModal" tabindex="-1" role="dialog" aria-labelledby="returnModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="returnModalLabel">归还确认</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                您确定要归还《<span id="returnBookTitle"></span>》吗?
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-success" id="confirmReturn">确认归还</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 续借确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="renewModal" tabindex="-1" role="dialog" aria-labelledby="renewModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="renewModalLabel">续借确认</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                您确定要续借《<span id="renewBookTitle"></span>》吗?续借后将延长14天的借阅期限。
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-primary" id="confirmRenew">确认续借</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 通知确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="notifyModal" tabindex="-1" role="dialog" aria-labelledby="notifyModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="notifyModalLabel">发送逾期通知</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                您确定要发送《<span id="notifyBookTitle"></span>》的逾期通知吗?
 | 
				
			||||||
 | 
					                此操作将向借阅用户发送逾期提醒消息。
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-warning" id="confirmNotify">发送通知</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/borrow_management.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										186
									
								
								app/templates/borrow/my_borrows.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								app/templates/borrow/my_borrows.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}我的梦幻书架 - 图书管理系统{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					{{ super() }}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/my_borrows.css') }}">
 | 
				
			||||||
 | 
					<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap" rel="stylesheet">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container">
 | 
				
			||||||
 | 
					    <h1 class="page-title">✨ 我的梦幻书架 ✨</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="tabs">
 | 
				
			||||||
 | 
					        <a href="{{ url_for('borrow.my_borrows', status=1) }}" class="tab {% if status == 1 %}active{% endif %}">
 | 
				
			||||||
 | 
					            <i class="fas fa-book-open"></i> 当前借阅 <span class="count">{{ current_borrows_count }}</span>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <a href="{{ url_for('borrow.my_borrows', status=0) }}" class="tab {% if status == 0 %}active{% endif %}">
 | 
				
			||||||
 | 
					            <i class="fas fa-history"></i> 历史借阅 <span class="count">{{ history_borrows_count }}</span>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <a href="{{ url_for('borrow.my_borrows') }}" class="tab {% if status is none %}active{% endif %}">
 | 
				
			||||||
 | 
					            <i class="fas fa-heart"></i> 全部借阅 <span class="count">{{ current_borrows_count + history_borrows_count }}</span>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="borrow-list">
 | 
				
			||||||
 | 
					        {% if pagination.items %}
 | 
				
			||||||
 | 
					            <table class="borrow-table">
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th>图书封面</th>
 | 
				
			||||||
 | 
					                        <th>书名</th>
 | 
				
			||||||
 | 
					                        <th>借阅日期</th>
 | 
				
			||||||
 | 
					                        <th>应还日期</th>
 | 
				
			||||||
 | 
					                        <th>状态</th>
 | 
				
			||||||
 | 
					                        <th>操作</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    {% for borrow in pagination.items %}
 | 
				
			||||||
 | 
					                    <tr class="borrow-item {% if borrow.status == 1 and borrow.due_date < now %}overdue{% endif %}">
 | 
				
			||||||
 | 
					                        <td class="book-cover">
 | 
				
			||||||
 | 
					                            {% if borrow.book.cover_url %}
 | 
				
			||||||
 | 
					                                {% if borrow.book.cover_url.startswith('/') %}
 | 
				
			||||||
 | 
					                                    <img src="{{ borrow.book.cover_url }}" alt="{{ borrow.book.title }}">
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <img src="{{ url_for('static', filename='images/book-placeholder.jpg') }}" alt="{{ borrow.book.title }}">
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="book-title">
 | 
				
			||||||
 | 
					                            <a href="{{ url_for('book.book_detail', book_id=borrow.book_id) }}">{{ borrow.book.title }}</a>
 | 
				
			||||||
 | 
					                            <div class="book-author">{{ borrow.book.author }}</div>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>{{ borrow.borrow_date.strftime('%Y-%m-%d') }}</td>
 | 
				
			||||||
 | 
					                        <td class="due-date {% if borrow.status == 1 and borrow.due_date < now %}text-danger{% endif %}">
 | 
				
			||||||
 | 
					                            {{ borrow.due_date.strftime('%Y-%m-%d') }}
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 and borrow.due_date < now %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-danger">已逾期</span>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-primary">借阅中</span>
 | 
				
			||||||
 | 
					                                {% if borrow.renew_count > 0 %}
 | 
				
			||||||
 | 
					                                    <span class="badge badge-info">已续借{{ borrow.renew_count }}次</span>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <span class="badge badge-success">已归还</span>
 | 
				
			||||||
 | 
					                                <div class="return-date">{{ borrow.return_date.strftime('%Y-%m-%d') }}</div>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="actions">
 | 
				
			||||||
 | 
					                            {% if borrow.status == 1 %}
 | 
				
			||||||
 | 
					                                <button class="btn btn-sm btn-success return-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">归还</button>
 | 
				
			||||||
 | 
					                                {% if borrow.renew_count < 2 and borrow.due_date >= now %}
 | 
				
			||||||
 | 
					                                    <button class="btn btn-sm btn-primary renew-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">续借</button>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 分页 -->
 | 
				
			||||||
 | 
					            <div class="pagination-container">
 | 
				
			||||||
 | 
					                {% if pagination.pages > 1 %}
 | 
				
			||||||
 | 
					                <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					                    <ul class="pagination">
 | 
				
			||||||
 | 
					                        {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.my_borrows', page=pagination.prev_num, status=status) }}" aria-label="Previous">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true"><i class="fas fa-chevron-left"></i></span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                            {% if page_num %}
 | 
				
			||||||
 | 
					                                <li class="page-item {% if page_num == pagination.page %}active{% endif %}">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="{{ url_for('borrow.my_borrows', page=page_num, status=status) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <li class="page-item disabled"><span class="page-link">...</span></li>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.my_borrows', page=pagination.next_num, status=status) }}" aria-label="Next">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true"><i class="fas fa-chevron-right"></i></span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </nav>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					            <div class="no-records">
 | 
				
			||||||
 | 
					                <i class="fas fa-book-reader empty-icon"></i>
 | 
				
			||||||
 | 
					                <p class="empty-text">
 | 
				
			||||||
 | 
					                    {% if status == 1 %}
 | 
				
			||||||
 | 
					                        哎呀~你还没有借阅任何图书呢!快去探索那些等待与你相遇的故事吧~
 | 
				
			||||||
 | 
					                    {% elif status == 0 %}
 | 
				
			||||||
 | 
					                        亲爱的,你还没有归还过任何图书呢~一起开启阅读的奇妙旅程吧!
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                        你的书架空空如也~赶快挑选几本心动的书籍,开启你的阅读冒险吧!
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					                <a href="{{ url_for('book.book_list') }}" class="btn btn-primary">
 | 
				
			||||||
 | 
					                    <i class="fas fa-heart"></i> 探索好书
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 归还确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="returnModal" tabindex="-1" role="dialog" aria-labelledby="returnModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="returnModalLabel"><i class="fas fa-heart"></i> 归还确认</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                亲爱的读者,你确定要归还《<span id="returnBookTitle"></span>》这本书吗?希望它带给你美好的阅读体验~
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">再想想</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-success" id="confirmReturn"><i class="fas fa-check"></i> 确认归还</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 续借确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="renewModal" tabindex="-1" role="dialog" aria-labelledby="renewModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="renewModalLabel"><i class="fas fa-magic"></i> 续借确认</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                亲爱的读者,想要与《<span id="renewBookTitle"></span>》多相处一段时间吗?续借后将延长14天的阅读时光哦~
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">再想想</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-primary" id="confirmRenew"><i class="fas fa-check"></i> 确认续借</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/my_borrows.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										179
									
								
								app/templates/borrow/overdue.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								app/templates/borrow/overdue.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}逾期管理 - 图书管理系统{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/overdue.css') }}">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container">
 | 
				
			||||||
 | 
					    <div class="d-flex justify-content-between align-items-center mb-4">
 | 
				
			||||||
 | 
					        <h1 class="page-title">逾期管理</h1>
 | 
				
			||||||
 | 
					        <a href="{{ url_for('borrow.manage_borrows') }}" class="btn btn-outline-secondary">
 | 
				
			||||||
 | 
					            <i class="fas fa-arrow-left"></i> 返回借阅管理
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="alert alert-warning">
 | 
				
			||||||
 | 
					        <i class="fas fa-exclamation-triangle"></i>
 | 
				
			||||||
 | 
					        当前共有 <strong>{{ overdue_count }}</strong> 条逾期未归还的借阅记录,请及时处理。
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="overdue-list">
 | 
				
			||||||
 | 
					        {% if pagination.items %}
 | 
				
			||||||
 | 
					            <table class="overdue-table">
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th width="10%">图书封面</th>
 | 
				
			||||||
 | 
					                        <th width="20%">书名</th>
 | 
				
			||||||
 | 
					                        <th width="15%">借阅用户</th>
 | 
				
			||||||
 | 
					                        <th width="12%">借阅日期</th>
 | 
				
			||||||
 | 
					                        <th width="12%">应还日期</th>
 | 
				
			||||||
 | 
					                        <th width="15%">逾期天数</th>
 | 
				
			||||||
 | 
					                        <th width="16%">操作</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    {% for borrow in pagination.items %}
 | 
				
			||||||
 | 
					                    <tr class="overdue-item">
 | 
				
			||||||
 | 
					                        <td class="book-cover">
 | 
				
			||||||
 | 
					                            <img src="{{ url_for('static', filename='covers/' + borrow.book.cover_url) if borrow.book.cover_url else url_for('static', filename='images/book-placeholder.jpg') }}" alt="{{ borrow.book.title }}">
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="book-title">
 | 
				
			||||||
 | 
					                            <a href="{{ url_for('book.book_detail', book_id=borrow.book_id) }}">{{ borrow.book.title }}</a>
 | 
				
			||||||
 | 
					                            <div class="book-author">{{ borrow.book.author }}</div>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="user-info">
 | 
				
			||||||
 | 
					                            <a href="{{ url_for('user.user_edit', user_id=borrow.user_id) }}">{{ borrow.user.username }}</a>
 | 
				
			||||||
 | 
					                            {% if borrow.user.nickname %}
 | 
				
			||||||
 | 
					                                <div class="user-nickname">{{ borrow.user.nickname }}</div>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                            <div class="user-contact">
 | 
				
			||||||
 | 
					                                {% if borrow.user.email %}
 | 
				
			||||||
 | 
					                                    <a href="mailto:{{ borrow.user.email }}" class="email-link">
 | 
				
			||||||
 | 
					                                        <i class="fas fa-envelope"></i>
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                                {% if borrow.user.phone %}
 | 
				
			||||||
 | 
					                                    <a href="tel:{{ borrow.user.phone }}" class="phone-link">
 | 
				
			||||||
 | 
					                                        <i class="fas fa-phone"></i>
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>{{ borrow.borrow_date.strftime('%Y-%m-%d') }}</td>
 | 
				
			||||||
 | 
					                        <td class="due-date text-danger">
 | 
				
			||||||
 | 
					                            {{ borrow.due_date.strftime('%Y-%m-%d') }}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="overdue-days">
 | 
				
			||||||
 | 
					                            {% set days_overdue = ((now - borrow.due_date).days) %}
 | 
				
			||||||
 | 
					                            <span class="badge {% if days_overdue > 30 %}badge-danger{% elif days_overdue > 14 %}badge-warning{% else %}badge-info{% endif %}">
 | 
				
			||||||
 | 
					                                {{ days_overdue }} 天
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td class="actions">
 | 
				
			||||||
 | 
					                            <button class="btn btn-sm btn-success return-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">归还处理</button>
 | 
				
			||||||
 | 
					                            <button class="btn btn-sm btn-warning notify-btn" data-id="{{ borrow.id }}" data-title="{{ borrow.book.title }}">发送通知</button>
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 分页 -->
 | 
				
			||||||
 | 
					            <div class="pagination-container">
 | 
				
			||||||
 | 
					                {% if pagination.pages > 1 %}
 | 
				
			||||||
 | 
					                <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					                    <ul class="pagination">
 | 
				
			||||||
 | 
					                        {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.overdue_borrows', page=pagination.prev_num) }}" aria-label="Previous">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true">«</span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                            {% if page_num %}
 | 
				
			||||||
 | 
					                                <li class="page-item {% if page_num == pagination.page %}active{% endif %}">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="{{ url_for('borrow.overdue_borrows', page=page_num) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <li class="page-item disabled"><span class="page-link">...</span></li>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('borrow.overdue_borrows', page=pagination.next_num) }}" aria-label="Next">
 | 
				
			||||||
 | 
					                                    <span aria-hidden="true">»</span>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </nav>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					            <div class="no-records">
 | 
				
			||||||
 | 
					                <i class="fas fa-check-circle empty-icon"></i>
 | 
				
			||||||
 | 
					                <p class="empty-text">目前没有逾期的借阅记录,继续保持!</p>
 | 
				
			||||||
 | 
					                <a href="{{ url_for('borrow.manage_borrows') }}" class="btn btn-primary">返回借阅管理</a>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 归还确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="returnModal" tabindex="-1" role="dialog" aria-labelledby="returnModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="returnModalLabel">逾期归还处理</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                <p>您正在处理《<span id="returnBookTitle"></span>》的逾期归还:</p>
 | 
				
			||||||
 | 
					                <div class="form-group">
 | 
				
			||||||
 | 
					                    <label for="overdueRemark">备注信息(可选)</label>
 | 
				
			||||||
 | 
					                    <textarea class="form-control" id="overdueRemark" rows="3" placeholder="可以输入处理结果、是否收取逾期费用等信息..."></textarea>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-success" id="confirmReturn">确认归还</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 通知确认模态框 -->
 | 
				
			||||||
 | 
					<div class="modal fade" id="notifyModal" tabindex="-1" role="dialog" aria-labelledby="notifyModalLabel" aria-hidden="true">
 | 
				
			||||||
 | 
					    <div class="modal-dialog" role="document">
 | 
				
			||||||
 | 
					        <div class="modal-content">
 | 
				
			||||||
 | 
					            <div class="modal-header">
 | 
				
			||||||
 | 
					                <h5 class="modal-title" id="notifyModalLabel">发送逾期通知</h5>
 | 
				
			||||||
 | 
					                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
				
			||||||
 | 
					                    <span aria-hidden="true">×</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-body">
 | 
				
			||||||
 | 
					                您确定要发送《<span id="notifyBookTitle"></span>》的逾期通知吗?
 | 
				
			||||||
 | 
					                此操作将向借阅用户发送逾期提醒消息。
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="modal-footer">
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
				
			||||||
 | 
					                <button type="button" class="btn btn-warning" id="confirmNotify">发送通知</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/overdue.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										96
									
								
								app/templates/inventory/adjust.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								app/templates/inventory/adjust.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}调整库存 - {{ book.title }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/inventory-adjust.css') }}">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container mt-5">
 | 
				
			||||||
 | 
					    <div class="disney-inventory-card">
 | 
				
			||||||
 | 
					        <div class="disney-decoration top-left"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration top-right"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration bottom-left"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration bottom-right"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="card-header-disney">
 | 
				
			||||||
 | 
					            <div class="mickey-ears"></div>
 | 
				
			||||||
 | 
					            <h4>调整图书库存</h4>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="card-body-disney">
 | 
				
			||||||
 | 
					            <div class="row mb-4">
 | 
				
			||||||
 | 
					                <div class="col-md-4 book-cover-container">
 | 
				
			||||||
 | 
					                    {% if book.cover_url %}
 | 
				
			||||||
 | 
					                    <img src="{{ book.cover_url }}" alt="{{ book.title }}" class="img-fluid book-cover">
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <img src="{{ url_for('static', filename='images/book-placeholder.jpg') }}" alt="默认封面" class="img-fluid book-cover">
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    <div class="disney-sparkles"></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="col-md-8 book-details">
 | 
				
			||||||
 | 
					                    <h3 class="book-title">{{ book.title }}</h3>
 | 
				
			||||||
 | 
					                    <div class="book-info">
 | 
				
			||||||
 | 
					                        <p><span class="disney-icon author-icon"></span> <strong>作者:</strong> {{ book.author }}</p>
 | 
				
			||||||
 | 
					                        <p><span class="disney-icon publisher-icon"></span> <strong>出版社:</strong> {{ book.publisher }}</p>
 | 
				
			||||||
 | 
					                        <p><span class="disney-icon isbn-icon"></span> <strong>ISBN:</strong> {{ book.isbn }}</p>
 | 
				
			||||||
 | 
					                        <p><span class="disney-icon inventory-icon"></span> <strong>当前库存:</strong>
 | 
				
			||||||
 | 
					                            <span class="stock-badge {{ 'high-stock' if book.stock > 5 else 'low-stock' if book.stock > 0 else 'out-stock' }}">
 | 
				
			||||||
 | 
					                                {{ book.stock }}
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-container">
 | 
				
			||||||
 | 
					                <form method="POST" action="{{ url_for('inventory.adjust_inventory', book_id=book.id) }}">
 | 
				
			||||||
 | 
					                    <div class="mb-4 form-group">
 | 
				
			||||||
 | 
					                        <label for="change_type" class="form-label disney-label">
 | 
				
			||||||
 | 
					                            <span class="disney-icon type-icon"></span> 调整类型
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                        <select class="form-select disney-select" id="change_type" name="change_type" required>
 | 
				
			||||||
 | 
					                            <option value="in">入库(增加库存)</option>
 | 
				
			||||||
 | 
					                            <option value="out">出库(减少库存)</option>
 | 
				
			||||||
 | 
					                        </select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="mb-4 form-group">
 | 
				
			||||||
 | 
					                        <label for="change_amount" class="form-label disney-label">
 | 
				
			||||||
 | 
					                            <span class="disney-icon amount-icon"></span> 调整数量
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                        <input type="number" class="form-control disney-input" id="change_amount" name="change_amount" min="1" value="1" required>
 | 
				
			||||||
 | 
					                        <div class="form-text stock-hint" id="stock-hint">当前库存: {{ book.stock }}</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="mb-4 form-group">
 | 
				
			||||||
 | 
					                        <label for="remark" class="form-label disney-label">
 | 
				
			||||||
 | 
					                            <span class="disney-icon remark-icon"></span> 备注
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                        <textarea class="form-control disney-textarea" id="remark" name="remark" rows="3" placeholder="填写库存调整原因,如:新书入库、丢失、损坏等"></textarea>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="button-group">
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('inventory.inventory_list') }}" class="btn disney-cancel-btn">
 | 
				
			||||||
 | 
					                            取消
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        <button type="submit" class="btn disney-confirm-btn">
 | 
				
			||||||
 | 
					                            确认调整
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    // 将当前库存数量传递给JavaScript
 | 
				
			||||||
 | 
					    const CURRENT_STOCK = {{ book.stock }};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/inventory-adjust.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										186
									
								
								app/templates/inventory/book_logs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								app/templates/inventory/book_logs.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}《{{ book.title }}》库存日志{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/inventory-book-logs.css') }}">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="frozen-background">
 | 
				
			||||||
 | 
					    <div class="snowflakes" aria-hidden="true">
 | 
				
			||||||
 | 
					        <div class="snowflake">❅</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❆</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❅</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❆</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❅</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❆</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❅</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❆</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❅</div>
 | 
				
			||||||
 | 
					        <div class="snowflake">❆</div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="container mt-5">
 | 
				
			||||||
 | 
					        <div class="frozen-card">
 | 
				
			||||||
 | 
					            <div class="castle-decoration"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="card-header-frozen">
 | 
				
			||||||
 | 
					                <div class="ice-crystal left"></div>
 | 
				
			||||||
 | 
					                <h4><i class="fas fa-book-open"></i> 《{{ book.title }}》库存变动日志</h4>
 | 
				
			||||||
 | 
					                <div class="ice-crystal right"></div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="card-body-frozen">
 | 
				
			||||||
 | 
					                <div class="row mb-4 book-info-row">
 | 
				
			||||||
 | 
					                    <div class="col-md-3 book-cover-container">
 | 
				
			||||||
 | 
					                        <div class="book-frame">
 | 
				
			||||||
 | 
					                            {% if book.cover_url %}
 | 
				
			||||||
 | 
					                            <img src="{{ book.cover_url }}" alt="{{ book.title }}" class="img-fluid book-cover">
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                            <img src="{{ url_for('static', filename='images/book-placeholder.jpg') }}" alt="默认封面" class="img-fluid book-cover">
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                            <div class="book-glow"></div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-md-9 book-details">
 | 
				
			||||||
 | 
					                        <h3 class="book-title">{{ book.title }}</h3>
 | 
				
			||||||
 | 
					                        <div class="book-info">
 | 
				
			||||||
 | 
					                            <p class="info-item"><i class="fas fa-feather-alt"></i> <strong>作者:</strong> {{ book.author }}</p>
 | 
				
			||||||
 | 
					                            <p class="info-item"><i class="fas fa-building"></i> <strong>出版社:</strong> {{ book.publisher }}</p>
 | 
				
			||||||
 | 
					                            <p class="info-item"><i class="fas fa-barcode"></i> <strong>ISBN:</strong> {{ book.isbn }}</p>
 | 
				
			||||||
 | 
					                            <p class="info-item">
 | 
				
			||||||
 | 
					                                <i class="fas fa-cubes"></i> <strong>当前库存:</strong>
 | 
				
			||||||
 | 
					                                <span class="frozen-badge {{ 'high-stock' if book.stock > 5 else 'low-stock' if book.stock > 0 else 'out-stock' }}">
 | 
				
			||||||
 | 
					                                    {{ book.stock }}
 | 
				
			||||||
 | 
					                                </span>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="history-section">
 | 
				
			||||||
 | 
					                    <h5 class="section-title">
 | 
				
			||||||
 | 
					                        <i class="fas fa-history"></i> 库存变动历史记录
 | 
				
			||||||
 | 
					                        <div class="magic-underline"></div>
 | 
				
			||||||
 | 
					                    </h5>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <div class="table-container">
 | 
				
			||||||
 | 
					                        <div class="table-frozen">
 | 
				
			||||||
 | 
					                            <div class="table-header-row">
 | 
				
			||||||
 | 
					                                <div class="th-frozen">ID</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">操作类型</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">变动数量</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">变动后库存</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">操作人</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">备注</div>
 | 
				
			||||||
 | 
					                                <div class="th-frozen">操作时间</div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <div class="table-body">
 | 
				
			||||||
 | 
					                                {% for log in logs %}
 | 
				
			||||||
 | 
					                                <div class="table-row log-entry" data-type="{{ log.change_type }}">
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">{{ log.id }}</div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">
 | 
				
			||||||
 | 
					                                        <span class="operation-badge {{ 'in-badge' if log.change_type == 'in' else 'out-badge' }}">
 | 
				
			||||||
 | 
					                                            {{ '入库' if log.change_type == 'in' else '出库' }}
 | 
				
			||||||
 | 
					                                            <i class="fas {{ 'fa-arrow-circle-down' if log.change_type == 'in' else 'fa-arrow-circle-up' }}"></i>
 | 
				
			||||||
 | 
					                                        </span>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">{{ log.change_amount }}</div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">{{ log.after_stock }}</div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">{{ log.operator.username if log.operator else '系统' }}</div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen remark-cell">{{ log.remark or '-' }}</div>
 | 
				
			||||||
 | 
					                                    <div class="td-frozen">{{ log.changed_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                {% if not logs %}
 | 
				
			||||||
 | 
					                                <div class="table-row empty-log">
 | 
				
			||||||
 | 
					                                    <div class="td-frozen empty-message" colspan="7">
 | 
				
			||||||
 | 
					                                        <div class="olaf-empty">
 | 
				
			||||||
 | 
					                                            <div class="olaf-image"></div>
 | 
				
			||||||
 | 
					                                            <p>暂无库存变动记录,要不要堆个雪人?</p>
 | 
				
			||||||
 | 
					                                        </div>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- 分页 -->
 | 
				
			||||||
 | 
					                    <div class="pagination-container">
 | 
				
			||||||
 | 
					                        <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					                            <ul class="frozen-pagination">
 | 
				
			||||||
 | 
					                                {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                                <li class="page-item">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="{{ url_for('inventory.book_inventory_logs', book_id=book.id, page=pagination.prev_num) }}">
 | 
				
			||||||
 | 
					                                        <i class="fas fa-chevron-left"></i> 上一页
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                                {% else %}
 | 
				
			||||||
 | 
					                                <li class="page-item disabled">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="#">
 | 
				
			||||||
 | 
					                                        <i class="fas fa-chevron-left"></i> 上一页
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                                    {% if page_num %}
 | 
				
			||||||
 | 
					                                        {% if page_num == pagination.page %}
 | 
				
			||||||
 | 
					                                        <li class="page-item active">
 | 
				
			||||||
 | 
					                                            <a class="page-link" href="#">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                        </li>
 | 
				
			||||||
 | 
					                                        {% else %}
 | 
				
			||||||
 | 
					                                        <li class="page-item">
 | 
				
			||||||
 | 
					                                            <a class="page-link" href="{{ url_for('inventory.book_inventory_logs', book_id=book.id, page=page_num) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                        </li>
 | 
				
			||||||
 | 
					                                        {% endif %}
 | 
				
			||||||
 | 
					                                    {% else %}
 | 
				
			||||||
 | 
					                                        <li class="page-item disabled">
 | 
				
			||||||
 | 
					                                            <a class="page-link" href="#">...</a>
 | 
				
			||||||
 | 
					                                        </li>
 | 
				
			||||||
 | 
					                                    {% endif %}
 | 
				
			||||||
 | 
					                                {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                                <li class="page-item">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="{{ url_for('inventory.book_inventory_logs', book_id=book.id, page=pagination.next_num) }}">
 | 
				
			||||||
 | 
					                                        下一页 <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                                {% else %}
 | 
				
			||||||
 | 
					                                <li class="page-item disabled">
 | 
				
			||||||
 | 
					                                    <a class="page-link" href="#">
 | 
				
			||||||
 | 
					                                        下一页 <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </li>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                        </nav>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="card-footer-frozen">
 | 
				
			||||||
 | 
					                <div class="footer-actions">
 | 
				
			||||||
 | 
					                    <a href="{{ url_for('inventory.inventory_list') }}" class="btn frozen-btn return-btn">
 | 
				
			||||||
 | 
					                        <i class="fas fa-arrow-left"></i> 返回库存管理
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="{{ url_for('inventory.adjust_inventory', book_id=book.id) }}" class="btn frozen-btn adjust-btn">
 | 
				
			||||||
 | 
					                        <i class="fas fa-sliders-h"></i> 调整库存
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="footer-decoration"></div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/inventory-book-logs.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										135
									
								
								app/templates/inventory/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								app/templates/inventory/list.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}图书库存管理{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/inventory-list.css') }}">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="inventory-container">
 | 
				
			||||||
 | 
					    <div class="page-header">
 | 
				
			||||||
 | 
					        <div class="header-content">
 | 
				
			||||||
 | 
					            <h1><i class="fas fa-book-open header-icon"></i>图书库存管理</h1>
 | 
				
			||||||
 | 
					            <p class="subtitle">优雅管理您的书籍资源</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="search-card">
 | 
				
			||||||
 | 
					        <form method="GET" action="{{ url_for('inventory.inventory_list') }}" class="search-form">
 | 
				
			||||||
 | 
					            <div class="search-input-group">
 | 
				
			||||||
 | 
					                <div class="search-input-container">
 | 
				
			||||||
 | 
					                    <i class="fas fa-search search-icon"></i>
 | 
				
			||||||
 | 
					                    <input type="text" class="search-input" name="search" placeholder="搜索书名、作者或ISBN" value="{{ search }}">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <button class="search-button" type="submit">搜索</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <a href="{{ url_for('inventory.inventory_logs') }}" class="log-button">
 | 
				
			||||||
 | 
					                <i class="fas fa-history"></i> 查看库存日志
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="table-container">
 | 
				
			||||||
 | 
					        <table class="inventory-table">
 | 
				
			||||||
 | 
					            <thead>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th>ID</th>
 | 
				
			||||||
 | 
					                    <th>书名</th>
 | 
				
			||||||
 | 
					                    <th>作者</th>
 | 
				
			||||||
 | 
					                    <th>ISBN</th>
 | 
				
			||||||
 | 
					                    <th>当前库存</th>
 | 
				
			||||||
 | 
					                    <th>状态</th>
 | 
				
			||||||
 | 
					                    <th>操作</th>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </thead>
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					                {% for book in books %}
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>{{ book.id }}</td>
 | 
				
			||||||
 | 
					                    <td class="book-title">{{ book.title }}</td>
 | 
				
			||||||
 | 
					                    <td class="book-author">{{ book.author }}</td>
 | 
				
			||||||
 | 
					                    <td>{{ book.isbn }}</td>
 | 
				
			||||||
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        <span class="stock-badge {{ 'stock-high' if book.stock > 5 else 'stock-medium' if book.stock > 0 else 'stock-low' }}">
 | 
				
			||||||
 | 
					                            {{ book.stock }}
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        <span class="status-badge {{ 'status-active' if book.status == 1 else 'status-inactive' }}">
 | 
				
			||||||
 | 
					                            {{ '正常' if book.status == 1 else '已下架' }}
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td class="action-buttons">
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('inventory.adjust_inventory', book_id=book.id) }}" class="btn-adjust">
 | 
				
			||||||
 | 
					                            <i class="fas fa-edit"></i> 调整
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        <a href="{{ url_for('inventory.book_inventory_logs', book_id=book.id) }}" class="btn-view">
 | 
				
			||||||
 | 
					                            <i class="fas fa-list-alt"></i> 日志
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- 分页 -->
 | 
				
			||||||
 | 
					    <div class="pagination-wrapper">
 | 
				
			||||||
 | 
					        <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					            <ul class="pagination">
 | 
				
			||||||
 | 
					                {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                <li class="page-item">
 | 
				
			||||||
 | 
					                    <a class="page-link" href="{{ url_for('inventory.inventory_list', page=pagination.prev_num, search=search, sort=sort, order=order) }}">
 | 
				
			||||||
 | 
					                        <i class="fas fa-chevron-left"></i> 上一页
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                <li class="page-item disabled">
 | 
				
			||||||
 | 
					                    <a class="page-link" href="#">
 | 
				
			||||||
 | 
					                        <i class="fas fa-chevron-left"></i> 上一页
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                    {% if page_num %}
 | 
				
			||||||
 | 
					                        {% if page_num == pagination.page %}
 | 
				
			||||||
 | 
					                        <li class="page-item active">
 | 
				
			||||||
 | 
					                            <a class="page-link" href="#">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        {% else %}
 | 
				
			||||||
 | 
					                        <li class="page-item">
 | 
				
			||||||
 | 
					                            <a class="page-link" href="{{ url_for('inventory.inventory_list', page=page_num, search=search, sort=sort, order=order) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                        <li class="page-item disabled">
 | 
				
			||||||
 | 
					                            <a class="page-link" href="#">...</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                <li class="page-item">
 | 
				
			||||||
 | 
					                    <a class="page-link" href="{{ url_for('inventory.inventory_list', page=pagination.next_num, search=search, sort=sort, order=order) }}">
 | 
				
			||||||
 | 
					                        下一页 <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                <li class="page-item disabled">
 | 
				
			||||||
 | 
					                    <a class="page-link" href="#">
 | 
				
			||||||
 | 
					                        下一页 <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					        </nav>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/inventory-list.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										210
									
								
								app/templates/inventory/logs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								app/templates/inventory/logs.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					{% extends 'base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}《{{ book.title }}》库存日志{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/inventory-logs.css') }}">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="disney-container"
 | 
				
			||||||
 | 
					    <!-- 雪花/魔法效果层 -->
 | 
				
			||||||
 | 
					    <div id="magic-particles"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- 主内容区 -->
 | 
				
			||||||
 | 
					    <div class="disney-card">
 | 
				
			||||||
 | 
					        <!-- 装饰元素 -->
 | 
				
			||||||
 | 
					        <div class="disney-decoration book-icon"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration crown-icon"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration wand-icon"></div>
 | 
				
			||||||
 | 
					        <div class="disney-decoration snowflake-icon"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- 卡片头部 -->
 | 
				
			||||||
 | 
					        <div class="card-header-disney">
 | 
				
			||||||
 | 
					            <div class="princess-crown"></div>
 | 
				
			||||||
 | 
					            <h4><i class="fas fa-book-open"></i>
 | 
				
			||||||
 | 
					              {% if book %}
 | 
				
			||||||
 | 
					                《{{ book.title }}》库存变动日志
 | 
				
			||||||
 | 
					              {% else %}
 | 
				
			||||||
 | 
					                全部库存变动日志
 | 
				
			||||||
 | 
					              {% endif %}
 | 
				
			||||||
 | 
					            </h4>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- 卡片内容 -->
 | 
				
			||||||
 | 
					        <div class="card-body-disney">
 | 
				
			||||||
 | 
					            <!-- 图书信息部分 -->
 | 
				
			||||||
 | 
					            {% if book %}
 | 
				
			||||||
 | 
					            <div class="book-details-container">
 | 
				
			||||||
 | 
					                <div class="book-cover-wrapper">
 | 
				
			||||||
 | 
					                    {% if book.cover_url %}
 | 
				
			||||||
 | 
					                    <img src="{{ book.cover_url }}" alt="{{ book.title }}" class="disney-book-cover">
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                    <img src="{{ url_for('static', filename='images/book-placeholder.jpg') }}" alt="默认封面" class="disney-book-cover">
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                    <div class="book-cover-glow"></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="book-info">
 | 
				
			||||||
 | 
					                    <h3 class="book-title">{{ book.title }}</h3>
 | 
				
			||||||
 | 
					                    <div class="info-row">
 | 
				
			||||||
 | 
					                        <div class="disney-icon author-icon"></div>
 | 
				
			||||||
 | 
					                        <div><strong>作者:</strong> {{ book.author }}</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="info-row">
 | 
				
			||||||
 | 
					                        <div class="disney-icon publisher-icon"></div>
 | 
				
			||||||
 | 
					                        <div><strong>出版社:</strong> {{ book.publisher }}</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="info-row">
 | 
				
			||||||
 | 
					                        <div class="disney-icon isbn-icon"></div>
 | 
				
			||||||
 | 
					                        <div><strong>ISBN:</strong> {{ book.isbn }}</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="info-row">
 | 
				
			||||||
 | 
					                        <div class="disney-icon stock-icon"></div>
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            <strong>当前库存:</strong>
 | 
				
			||||||
 | 
					                            <span class="stock-badge {{ 'high-stock' if book.stock > 5 else 'low-stock' if book.stock > 0 else 'out-stock' }}">
 | 
				
			||||||
 | 
					                                {{ book.stock }}
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- 日志表格部分 -->
 | 
				
			||||||
 | 
					            <div class="logs-section">
 | 
				
			||||||
 | 
					                <h5 class="logs-title">
 | 
				
			||||||
 | 
					                    <div class="title-decoration left"></div>
 | 
				
			||||||
 | 
					                    库存变动历史记录
 | 
				
			||||||
 | 
					                    <div class="title-decoration right"></div>
 | 
				
			||||||
 | 
					                </h5>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="table-container">
 | 
				
			||||||
 | 
					                    <table class="disney-table">
 | 
				
			||||||
 | 
					                        <thead>
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <th>ID</th>
 | 
				
			||||||
 | 
					                                {% if not book %}<th>图书</th>{% endif %}
 | 
				
			||||||
 | 
					                                <th>操作类型</th>
 | 
				
			||||||
 | 
					                                <th>变动数量</th>
 | 
				
			||||||
 | 
					                                <th>变动后库存</th>
 | 
				
			||||||
 | 
					                                <th>操作人</th>
 | 
				
			||||||
 | 
					                                <th>备注</th>
 | 
				
			||||||
 | 
					                                <th>操作时间</th>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        </thead>
 | 
				
			||||||
 | 
					                        <tbody>
 | 
				
			||||||
 | 
					                            {% for log in logs %}
 | 
				
			||||||
 | 
					                            <tr class="log-row">
 | 
				
			||||||
 | 
					                                <td>{{ log.id }}</td>
 | 
				
			||||||
 | 
					                                {% if not book %}
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <a href="{{ url_for('inventory.book_inventory_logs', book_id=log.book_id) }}">
 | 
				
			||||||
 | 
					                                        {{ log.book.title if log.book else '未知图书' }}
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                                <td>
 | 
				
			||||||
 | 
					                                    <span class="operation-badge {{ 'in-badge' if log.change_type == 'in' or log.change_amount > 0 else 'out-badge' }}">
 | 
				
			||||||
 | 
					                                        {{ '入库' if log.change_type == 'in' or log.change_amount > 0 else '出库' }}
 | 
				
			||||||
 | 
					                                    </span>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                                <td>{{ log.change_amount }}</td>
 | 
				
			||||||
 | 
					                                <td>{{ log.after_stock }}</td>
 | 
				
			||||||
 | 
					                                <td>{{ log.operator.username if log.operator else '系统' }}</td>
 | 
				
			||||||
 | 
					                                <td class="remark-cell" title="{{ log.remark or '-' }}">{{ log.remark or '-' }}</td>
 | 
				
			||||||
 | 
					                                <td>{{ log.changed_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            {% if not logs %}
 | 
				
			||||||
 | 
					                            <tr>
 | 
				
			||||||
 | 
					                                <td colspan="{{ 8 if not book else 7 }}" class="empty-logs">
 | 
				
			||||||
 | 
					                                    <div class="empty-state">
 | 
				
			||||||
 | 
					                                        <div class="empty-icon"></div>
 | 
				
			||||||
 | 
					                                        <p>暂无库存变动记录</p>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </tbody>
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- 分页 -->
 | 
				
			||||||
 | 
					                <div class="disney-pagination">
 | 
				
			||||||
 | 
					                    <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					                        <ul class="pagination-list">
 | 
				
			||||||
 | 
					                            {% if pagination.has_prev %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('inventory.inventory_logs' if not book else 'inventory.book_inventory_logs', book_id=book.id if book else None, page=pagination.prev_num) }}">
 | 
				
			||||||
 | 
					                                    <i class="fas fa-chevron-left"></i> 上一页
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                            <li class="page-item disabled">
 | 
				
			||||||
 | 
					                                <span class="page-link"><i class="fas fa-chevron-left"></i> 上一页</span>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
 | 
				
			||||||
 | 
					                                {% if page_num %}
 | 
				
			||||||
 | 
					                                    {% if page_num == pagination.page %}
 | 
				
			||||||
 | 
					                                    <li class="page-item active">
 | 
				
			||||||
 | 
					                                        <span class="page-link">{{ page_num }}</span>
 | 
				
			||||||
 | 
					                                    </li>
 | 
				
			||||||
 | 
					                                    {% else %}
 | 
				
			||||||
 | 
					                                    <li class="page-item">
 | 
				
			||||||
 | 
					                                        <a class="page-link" href="{{ url_for('inventory.inventory_logs' if not book else 'inventory.book_inventory_logs', book_id=book.id if book else None, page=page_num) }}">{{ page_num }}</a>
 | 
				
			||||||
 | 
					                                    </li>
 | 
				
			||||||
 | 
					                                    {% endif %}
 | 
				
			||||||
 | 
					                                {% else %}
 | 
				
			||||||
 | 
					                                    <li class="page-item dots">
 | 
				
			||||||
 | 
					                                        <span class="page-link">...</span>
 | 
				
			||||||
 | 
					                                    </li>
 | 
				
			||||||
 | 
					                                {% endif %}
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            {% if pagination.has_next %}
 | 
				
			||||||
 | 
					                            <li class="page-item">
 | 
				
			||||||
 | 
					                                <a class="page-link" href="{{ url_for('inventory.inventory_logs' if not book else 'inventory.book_inventory_logs', book_id=book.id if book else None, page=pagination.next_num) }}">
 | 
				
			||||||
 | 
					                                    下一页 <i class="fas fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                                </a>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                            <li class="page-item disabled">
 | 
				
			||||||
 | 
					                                <span class="page-link">下一页 <i class="fas fa-chevron-right"></i></span>
 | 
				
			||||||
 | 
					                            </li>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
 | 
					                    </nav>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- 卡片底部 -->
 | 
				
			||||||
 | 
					        <div class="card-footer-disney">
 | 
				
			||||||
 | 
					            <div class="button-container">
 | 
				
			||||||
 | 
					                <a href="{{ url_for('inventory.inventory_list') }}" class="disney-button return-btn">
 | 
				
			||||||
 | 
					                    <span class="button-icon"><i class="fas fa-arrow-left"></i></span>
 | 
				
			||||||
 | 
					                    <span class="button-text">返回库存管理</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                {% if book %}
 | 
				
			||||||
 | 
					                <a href="{{ url_for('inventory.adjust_inventory', book_id=book.id) }}" class="disney-button adjust-btn">
 | 
				
			||||||
 | 
					                    <span class="button-icon"><i class="fas fa-edit"></i></span>
 | 
				
			||||||
 | 
					                    <span class="button-text">调整库存</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="https://cdnjs.cloudflare.com/ajax/libs/particles.js/2.0.0/particles.min.js"></script>
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/inventory-book-logs.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										120
									
								
								app/templates/user/add.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								app/templates/user/add.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}添加用户 - 图书管理系统{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block head %}
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="{{ url_for('static', filename='css/user-form.css') }}">
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="user-form-container">
 | 
				
			||||||
 | 
					    <div class="page-header">
 | 
				
			||||||
 | 
					        <h1>添加用户</h1>
 | 
				
			||||||
 | 
					        <div class="actions">
 | 
				
			||||||
 | 
					            <a href="{{ url_for('user.user_list') }}" class="btn btn-secondary">
 | 
				
			||||||
 | 
					                <i class="fas fa-arrow-left"></i> 返回用户列表
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="form-card">
 | 
				
			||||||
 | 
					        <form id="addUserForm" method="POST" action="{{ url_for('user.add_user') }}">
 | 
				
			||||||
 | 
					            {% if error %}
 | 
				
			||||||
 | 
					            <div class="alert alert-danger">{{ error }}</div>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="username">用户名</label>
 | 
				
			||||||
 | 
					                <input type="text" id="username" name="username" class="form-control" required
 | 
				
			||||||
 | 
					                       placeholder="请输入用户名(3-20个字符)" minlength="3" maxlength="20">
 | 
				
			||||||
 | 
					                <small class="form-text text-muted">用户名将用于登录,不可重复</small>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="password">密码</label>
 | 
				
			||||||
 | 
					                <div class="password-field">
 | 
				
			||||||
 | 
					                    <input type="password" id="password" name="password" class="form-control" required
 | 
				
			||||||
 | 
					                           placeholder="请输入密码(至少6位)" minlength="6">
 | 
				
			||||||
 | 
					                    <button type="button" class="toggle-password">
 | 
				
			||||||
 | 
					                        <i class="fas fa-eye"></i>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <small class="form-text text-muted">密码至少包含6个字符</small>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="confirm_password">确认密码</label>
 | 
				
			||||||
 | 
					                <div class="password-field">
 | 
				
			||||||
 | 
					                    <input type="password" id="confirm_password" name="confirm_password" class="form-control" required
 | 
				
			||||||
 | 
					                           placeholder="请再次输入密码" minlength="6">
 | 
				
			||||||
 | 
					                    <button type="button" class="toggle-password">
 | 
				
			||||||
 | 
					                        <i class="fas fa-eye"></i>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <small id="password-match-message" class="form-text"></small>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="email">电子邮箱</label>
 | 
				
			||||||
 | 
					                <div class="input-with-button">
 | 
				
			||||||
 | 
					                    <input type="email" id="email" name="email" class="form-control" required
 | 
				
			||||||
 | 
					                           placeholder="请输入有效的电子邮箱">
 | 
				
			||||||
 | 
					                    <button type="button" id="sendVerificationCode" class="btn btn-outline-primary">
 | 
				
			||||||
 | 
					                        发送验证码
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="verification_code">验证码</label>
 | 
				
			||||||
 | 
					                <input type="text" id="verification_code" name="verification_code" class="form-control" required
 | 
				
			||||||
 | 
					                       placeholder="请输入邮箱验证码" maxlength="6">
 | 
				
			||||||
 | 
					                <small class="form-text text-muted">请输入发送到邮箱的6位验证码</small>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="nickname">昵称</label>
 | 
				
			||||||
 | 
					                <input type="text" id="nickname" name="nickname" class="form-control"
 | 
				
			||||||
 | 
					                       placeholder="请输入昵称(选填)" maxlength="64">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="phone">手机号码</label>
 | 
				
			||||||
 | 
					                <input type="tel" id="phone" name="phone" class="form-control"
 | 
				
			||||||
 | 
					                       placeholder="请输入手机号码(选填)" maxlength="20">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="role_id">用户角色</label>
 | 
				
			||||||
 | 
					                <select id="role_id" name="role_id" class="form-control" required>
 | 
				
			||||||
 | 
					                    {% for role in roles %}
 | 
				
			||||||
 | 
					                    <option value="{{ role.id }}">{{ role.role_name }}</option>
 | 
				
			||||||
 | 
					                    {% endfor %}
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					                <small class="form-text text-muted">默认为普通用户,请根据需要选择合适的角色</small>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group required">
 | 
				
			||||||
 | 
					                <label for="status">账号状态</label>
 | 
				
			||||||
 | 
					                <select id="status" name="status" class="form-control" required>
 | 
				
			||||||
 | 
					                    <option value="1" selected>正常</option>
 | 
				
			||||||
 | 
					                    <option value="0">禁用</option>
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-actions">
 | 
				
			||||||
 | 
					                <button type="submit" class="btn btn-primary">
 | 
				
			||||||
 | 
					                    <i class="fas fa-save"></i> 保存用户
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                <button type="reset" class="btn btn-secondary">
 | 
				
			||||||
 | 
					                    <i class="fas fa-undo"></i> 重置表单
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="{{ url_for('static', filename='js/user-add.js') }}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
    <div class="page-header">
 | 
					    <div class="page-header">
 | 
				
			||||||
        <h1>用户管理</h1>
 | 
					        <h1>用户管理</h1>
 | 
				
			||||||
        <div class="actions">
 | 
					        <div class="actions">
 | 
				
			||||||
            <a href="{{ url_for('user.register') }}" class="btn btn-primary">
 | 
					            <a href="{{ url_for('user.add_user') }}" class="btn btn-primary">
 | 
				
			||||||
                <i class="fas fa-user-plus"></i> 添加用户
 | 
					                <i class="fas fa-user-plus"></i> 添加用户
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10309
									
								
								code_collection.txt
									
									
									
									
									
								
							
							
						
						
									
										10309
									
								
								code_collection.txt
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user