This commit is contained in:
superlishunqin 2025-05-06 12:01:11 +08:00
parent 5f46d87528
commit 29914a4178
44 changed files with 20245 additions and 443 deletions

View File

@ -4,6 +4,7 @@ from app.models.user import db, User
from app.controllers.user import user_bp
from app.controllers.book import book_bp
from app.controllers.borrow import borrow_bp
from app.controllers.inventory import inventory_bp
from flask_login import LoginManager, current_user
import os
@ -48,6 +49,7 @@ def create_app(config=None):
app.register_blueprint(user_bp, url_prefix='/user')
app.register_blueprint(book_bp, url_prefix='/book')
app.register_blueprint(borrow_bp, url_prefix='/borrow')
app.register_blueprint(inventory_bp)
# 创建数据库表
with app.app_context():
@ -132,3 +134,7 @@ def create_app(config=None):
return s
return app
@app.context_processor
def inject_now():
return {'now': datetime.datetime.now()}

View File

@ -116,7 +116,8 @@ def book_detail(book_id):
# 如果用户是管理员,预先查询并排序借阅记录
borrow_records = []
if g.user.role_id == 1: # 假设 role_id 1 为管理员
# 防御性编程:确保 g.user 存在且有 role_id 属性
if hasattr(g, 'user') and g.user is not None and hasattr(g.user, 'role_id') and g.user.role_id == 1:
from app.models.borrow import BorrowRecord
borrow_records = BorrowRecord.query.filter_by(book_id=book_id).order_by(BorrowRecord.borrow_date.desc()).limit(
10).all()
@ -124,12 +125,13 @@ def book_detail(book_id):
return render_template(
'book/detail.html',
book=book,
current_user=g.user,
current_user=current_user, # 使用 flask_login 的 current_user 而不是 g.user
borrow_records=borrow_records,
now=now
)
# 添加图书页面
@book_bp.route('/add', methods=['GET', 'POST'])
@login_required
@ -283,6 +285,7 @@ def edit_book(book_id):
book = Book.query.get_or_404(book_id)
if request.method == 'POST':
# 获取表单数据
title = request.form.get('title')
author = request.form.get('author')
publisher = request.form.get('publisher')
@ -294,13 +297,72 @@ def edit_book(book_id):
price = request.form.get('price')
status = request.form.get('status', type=int)
# 基本验证
if not title or not author:
flash('书名和作者不能为空', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# ISBN验证
if isbn and isbn.strip(): # 确保ISBN不是空字符串
# 移除连字符和空格
clean_isbn = isbn.replace('-', '').replace(' ', '')
# 长度检查
if len(clean_isbn) != 10 and len(clean_isbn) != 13:
flash('ISBN必须是10位或13位', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# ISBN-10验证
if len(clean_isbn) == 10:
# 检查前9位是否为数字
if not clean_isbn[:9].isdigit():
flash('ISBN-10的前9位必须是数字', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# 检查最后一位是否为数字或'X'
if not (clean_isbn[9].isdigit() or clean_isbn[9].upper() == 'X'):
flash('ISBN-10的最后一位必须是数字或X', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# 校验和验证
sum = 0
for i in range(9):
sum += int(clean_isbn[i]) * (10 - i)
check_digit = 10 if clean_isbn[9].upper() == 'X' else int(clean_isbn[9])
sum += check_digit
if sum % 11 != 0:
flash('ISBN-10校验和无效', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# ISBN-13验证
if len(clean_isbn) == 13:
# 检查是否全是数字
if not clean_isbn.isdigit():
flash('ISBN-13必须全是数字', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# 校验和验证
sum = 0
for i in range(12):
sum += int(clean_isbn[i]) * (1 if i % 2 == 0 else 3)
check_digit = (10 - (sum % 10)) % 10
if check_digit != int(clean_isbn[12]):
flash('ISBN-13校验和无效', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# 处理库存变更
new_stock = request.form.get('stock', type=int)
new_stock = request.form.get('stock', type=int) or 0 # 默认为0而非None
if new_stock != book.stock:
from app.models.inventory import InventoryLog
change_amount = new_stock - book.stock
@ -346,11 +408,17 @@ def edit_book(book_id):
book.status = status
book.updated_at = datetime.datetime.now()
db.session.commit()
flash('图书信息更新成功', 'success')
return redirect(url_for('book.book_list'))
try:
db.session.commit()
flash('图书信息更新成功', 'success')
return redirect(url_for('book.book_list'))
except Exception as e:
db.session.rollback()
flash(f'保存失败: {str(e)}', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
# GET 请求
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
@ -622,3 +690,53 @@ def test_permissions():
<p>是否管理员: {'' if current_user.role_id == 1 else ''}</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,)

View File

@ -1,11 +1,11 @@
from flask import Blueprint, request, redirect, url_for, flash, g, jsonify
from flask import Blueprint, request, redirect, url_for, flash, render_template, jsonify
from flask_login import current_user, login_required
from app.models.book import Book
from app.models.borrow import BorrowRecord
from app.models.inventory import InventoryLog
from app.models.user import db # 修正:从 user 模型导入 db
from app.utils.auth import login_required
from app.models.user import db, User
import datetime
from app.utils.auth import admin_required
# 创建借阅蓝图
borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow')
@ -30,7 +30,7 @@ def borrow_book():
# 检查当前用户是否已借阅此书
existing_borrow = BorrowRecord.query.filter_by(
user_id=g.user.id,
user_id=current_user.id,
book_id=book_id,
status=1 # 1表示借阅中
).first()
@ -45,7 +45,7 @@ def borrow_book():
due_date = now + datetime.timedelta(days=borrow_days)
borrow_record = BorrowRecord(
user_id=g.user.id,
user_id=current_user.id,
book_id=book_id,
borrow_date=now,
due_date=due_date,
@ -67,7 +67,7 @@ def borrow_book():
change_type='借出',
change_amount=-1,
after_stock=book.stock,
operator_id=g.user.id,
operator_id=current_user.id,
remark='用户借书',
changed_at=now
)
@ -101,7 +101,7 @@ def add_borrow(book_id):
# 检查是否已借阅
existing_borrow = BorrowRecord.query.filter_by(
user_id=current_user.id, # 使用current_user
user_id=current_user.id,
book_id=book_id,
status=1 # 1表示借阅中
).first()
@ -118,7 +118,7 @@ def add_borrow(book_id):
due_date = now + datetime.timedelta(days=borrow_days)
borrow_record = BorrowRecord(
user_id=current_user.id, # 使用current_user
user_id=current_user.id,
book_id=book_id,
borrow_date=now,
due_date=due_date,
@ -140,7 +140,7 @@ def add_borrow(book_id):
change_type='借出',
change_amount=-1,
after_stock=book.stock,
operator_id=current_user.id, # 使用current_user
operator_id=current_user.id,
remark='用户借书',
changed_at=now
)
@ -158,3 +158,382 @@ def add_borrow(book_id):
'success': False,
'message': f'借阅失败: {str(e)}'
})
@borrow_bp.route('/return/<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)}'
})

View File

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

View File

@ -405,3 +405,70 @@ def get_role_user_count(role_id):
'message': f"查询失败: {str(e)}",
'count': 0
}), 500
@user_bp.route('/add', methods=['GET', 'POST'])
@login_required
@admin_required
def add_user():
roles = UserService.get_all_roles()
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
verification_code = request.form.get('verification_code')
nickname = request.form.get('nickname')
phone = request.form.get('phone')
if phone == '':
phone = None
nickname = request.form.get('nickname')
role_id = request.form.get('role_id', 2, type=int) # 默认为普通用户
status = request.form.get('status', 1, type=int) # 默认为启用状态
# 验证表单数据
if not username or not email or not password or not confirm_password or not verification_code:
return render_template('user/add.html', error='所有必填字段不能为空', roles=roles)
if password != confirm_password:
return render_template('user/add.html', error='两次输入的密码不匹配', roles=roles)
# 检查用户名和邮箱是否已存在
if User.query.filter_by(username=username).first():
return render_template('user/add.html', error='用户名已存在', roles=roles)
if User.query.filter_by(email=email).first():
return render_template('user/add.html', error='邮箱已被注册', roles=roles)
# 验证验证码
stored_code = verification_codes.get(email)
if not stored_code or stored_code != verification_code:
return render_template('user/add.html', error='验证码无效或已过期', roles=roles)
# 创建新用户
try:
new_user = User(
username=username,
password=password, # 密码会在模型中自动哈希
email=email,
nickname=nickname or username, # 如果未提供昵称,使用用户名
phone=phone,
role_id=role_id,
status=status
)
db.session.add(new_user)
db.session.commit()
# 清除验证码
verification_codes.delete(email)
flash('用户添加成功', 'success')
return redirect(url_for('user.user_list'))
except Exception as e:
db.session.rollback()
logging.error(f"用户添加失败: {str(e)}")
return render_template('user/add.html', error=f'添加用户失败: {str(e)}', roles=roles)
# GET请求显示添加用户表单
return render_template('user/add.html', roles=roles)

View File

@ -36,7 +36,8 @@ class Book(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
# 移除所有关系引用
# 添加与 InventoryLog 的关系
inventory_logs = db.relationship('InventoryLog', backref='book', lazy='dynamic')
def __repr__(self):
return f'<Book {self.title}>'

View File

@ -19,13 +19,14 @@ class User(db.Model, UserMixin):
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, username, password, email=None, phone=None, nickname=None, role_id=2):
def __init__(self, username, password, email=None, phone=None, nickname=None, role_id=2, status=1):
self.username = username
self.set_password(password)
self.email = email
self.phone = phone
self.nickname = nickname
self.role_id = role_id
self.status = status # 新增
@property
def is_active(self):

View File

@ -161,3 +161,24 @@ class UserService:
except Exception as e:
db.session.rollback()
return False, f"更新失败: {str(e)}"
@staticmethod
def create_user(data):
"""创建新用户"""
try:
new_user = User(
username=data['username'],
password=data['password'],
email=data['email'],
nickname=data.get('nickname') or data['username'],
phone=data.get('phone'),
role_id=data.get('role_id', 2), # 默认为普通用户
status=data.get('status', 1) # 默认为启用状态
)
db.session.add(new_user)
db.session.commit()
return True, '用户创建成功'
except Exception as e:
db.session.rollback()
logging.error(f"创建用户失败: {str(e)}")
return False, f'创建用户失败: {str(e)}'

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View 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;
}

View 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
View 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%;
}
}

View 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;
}

View 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%;
}
}

View 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;
}
}

View 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);
}
}

View 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
View 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;
}
}

View 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);
}
}

View File

@ -127,7 +127,7 @@ $(document).ready(function() {
}
// 初始化表单验证
ffunction initFormValidation() {
function initFormValidation() {
$('#bookForm').on('submit', function(e) {
// 如果表单正在提交中,阻止重复提交
if (isSubmitting) {

View 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");
});

View 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">&times;</span>
</button>
`;
document.body.appendChild(alertDiv);
// 3秒后自动消失
setTimeout(() => {
alertDiv.remove();
}, 3000);
}
});

292
app/static/js/browse.js Normal file
View 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;
});
}
});

View 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');
});
});
});

View 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);

View 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();
});
});
});

View 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
View 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');