================================================================================
File: ./config.py
================================================================================
import os
# 数据库配置
"""
DB_HOST = os.environ.get('DB_HOST', '27.124.22.104')
DB_PORT = os.environ.get('DB_PORT', '3306')
DB_USER = os.environ.get('DB_USER', 'book20250428')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'booksystem')
DB_NAME = os.environ.get('DB_NAME', 'book_system')
"""
DB_HOST = os.environ.get('DB_HOST', 'rm-bp1h5oqo8ld21viftro.mysql.rds.aliyuncs.com')
DB_PORT = os.environ.get('DB_PORT', '3306')
DB_USER = os.environ.get('DB_USER', 'shiqi')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'Shiqi1234!')
DB_NAME = os.environ.get('DB_NAME', 'book_system')
# 数据库连接字符串
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 应用密钥
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev_key_replace_in_production')
# 邮件配置
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.qq.com')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 587))
EMAIL_ENCRYPTION = os.environ.get('EMAIL_ENCRYPTION', 'starttls')
EMAIL_USERNAME = os.environ.get('EMAIL_USERNAME', '3399560459@qq.com')
EMAIL_PASSWORD = os.environ.get('EMAIL_PASSWORD', 'fzwhyirhbqdzcjgf')
EMAIL_FROM = os.environ.get('EMAIL_FROM', '3399560459@qq.com')
EMAIL_FROM_NAME = os.environ.get('EMAIL_FROM_NAME', 'BOOKSYSTEM_OFFICIAL')
# 会话配置
PERMANENT_SESSION_LIFETIME = 86400 * 7
================================================================================
File: ./all_file_output.py
================================================================================
import os
import sys
def collect_code_files(output_file="code_collection.txt"):
# 定义代码文件扩展名
code_extensions = [
'.py', '.java', '.cpp', '.c', '.h', '.hpp', '.cs',
'.js', '.html', '.css', '.php', '.go', '.rb',
'.swift', '.kt', '.ts', '.sh', '.pl', '.r'
]
# 定义要排除的目录
excluded_dirs = [
'venv', 'env', '.venv', '.env', 'virtualenv',
'__pycache__', 'node_modules', '.git', '.idea',
'dist', 'build', 'target', 'bin'
]
# 计数器
file_count = 0
# 打开输出文件
with open(output_file, 'w', encoding='utf-8') as out_file:
# 遍历当前目录及所有子目录
for root, dirs, files in os.walk('.'):
# 从dirs中移除排除的目录,这会阻止os.walk进入这些目录
dirs[:] = [d for d in dirs if d not in excluded_dirs]
for file in files:
# 获取文件扩展名
_, ext = os.path.splitext(file)
# 检查是否为代码文件
if ext.lower() in code_extensions:
file_path = os.path.join(root, file)
file_count += 1
# 写入文件路径作为分隔
out_file.write(f"\n{'=' * 80}\n")
out_file.write(f"File: {file_path}\n")
out_file.write(f"{'=' * 80}\n\n")
# 尝试读取文件内容并写入
try:
with open(file_path, 'r', encoding='utf-8') as code_file:
out_file.write(code_file.read())
except UnicodeDecodeError:
# 尝试用不同的编码
try:
with open(file_path, 'r', encoding='latin-1') as code_file:
out_file.write(code_file.read())
except Exception as e:
out_file.write(f"无法读取文件内容: {str(e)}\n")
except Exception as e:
out_file.write(f"读取文件时出错: {str(e)}\n")
print(f"已成功收集 {file_count} 个代码文件到 {output_file}")
if __name__ == "__main__":
# 如果提供了命令行参数,则使用它作为输出文件名
output_file = sys.argv[1] if len(sys.argv) > 1 else "code_collection.txt"
collect_code_files(output_file)
================================================================================
File: ./app.py
================================================================================
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=49666)
================================================================================
File: ./main.py
================================================================================
# 这是一个示例 Python 脚本。
# 按 ⌃R 执行或将其替换为您的代码。
# 按 双击 ⇧ 在所有地方搜索类、文件、工具窗口、操作和设置。
def print_hi(name):
# 在下面的代码行中使用断点来调试脚本。
print(f'Hi, {name}') # 按 ⌘F8 切换断点。
# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
print_hi('PyCharm')
# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
================================================================================
File: ./app/__init__.py
================================================================================
from flask import Flask, render_template, session, g, Markup, redirect, url_for, request
from flask_login import LoginManager
from app.models.database import db
from app.models.user import 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
from app.controllers.statistics import statistics_bp
from app.controllers.announcement import announcement_bp
from app.models.notification import Notification
from app.controllers.log import log_bp
import os
from datetime import datetime
login_manager = LoginManager()
def create_app(config=None):
app = Flask(__name__)
# 加载默认配置
app.config.from_object('config')
# 如果提供了配置对象,则加载它
if config:
if isinstance(config, dict):
app.config.update(config)
else:
app.config.from_object(config)
# 从环境变量指定的文件加载配置(如果有)
app.config.from_envvar('APP_CONFIG_FILE', silent=True)
# 初始化数据库
db.init_app(app)
# 初始化 Flask-Login
login_manager.init_app(app)
login_manager.login_view = 'user.login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
from app.utils.template_helpers import register_template_helpers
# 注册蓝图
register_template_helpers(app)
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(statistics_bp)
app.register_blueprint(inventory_bp)
app.register_blueprint(log_bp)
app.register_blueprint(announcement_bp, url_prefix='/announcement')
# 创建数据库表
with app.app_context():
# 先导入基础模型
from app.models.user import User, Role
from app.models.book import Book, Category
# 创建表
db.create_all()
# 再导入依赖模型 - 但不在这里定义关系
from app.models.borrow import BorrowRecord
from app.models.inventory import InventoryLog
from app.models.log import Log
# 移除这些重复的关系定义
# Book.borrow_records = db.relationship('BorrowRecord', backref='book', lazy='dynamic')
# Book.inventory_logs = db.relationship('InventoryLog', backref='book', lazy='dynamic')
# Category.books = db.relationship('Book', backref='category', lazy='dynamic')
# 创建默认角色
from app.models.user import Role
if not Role.query.filter_by(id=1).first():
admin_role = Role(id=1, role_name='管理员', description='系统管理员')
db.session.add(admin_role)
if not Role.query.filter_by(id=2).first():
user_role = Role(id=2, role_name='普通用户', description='普通用户')
db.session.add(user_role)
# 创建管理员账号
if not User.query.filter_by(username='admin').first():
admin = User(
username='admin',
password='admin123',
email='admin@example.com',
role_id=1,
nickname='系统管理员'
)
db.session.add(admin)
# 创建基础分类
from app.models.book import Category
if not Category.query.first():
categories = [
Category(name='文学', sort=1),
Category(name='计算机', sort=2),
Category(name='历史', sort=3),
Category(name='科学', sort=4),
Category(name='艺术', sort=5),
Category(name='经济', sort=6),
Category(name='哲学', sort=7),
Category(name='教育', sort=8)
]
db.session.add_all(categories)
db.session.commit()
# 添加缓存控制中间件
@app.after_request
def add_cache_headers(response):
# 为HTML页面和主页添加禁止缓存的头
if request.path == '/' or 'text/html' in response.headers.get('Content-Type', ''):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
response.headers['Vary'] = 'Cookie, Authorization'
return response
# 其余代码保持不变...
@app.before_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = User.query.get(user_id)
@app.route('/')
def index():
from app.models.book import Book
from app.models.user import User
from app.models.borrow import BorrowRecord
from app.models.announcement import Announcement
from app.models.notification import Notification
from sqlalchemy import func, desc
from flask_login import current_user
# 获取统计数据
stats = {
'total_books': Book.query.count(),
'total_users': User.query.count(),
'active_borrows': BorrowRecord.query.filter(BorrowRecord.return_date.is_(None)).count(),
'user_borrows': 0
}
# 如果用户已登录,获取其待还图书数量
if current_user.is_authenticated:
stats['user_borrows'] = BorrowRecord.query.filter(
BorrowRecord.user_id == current_user.id,
BorrowRecord.return_date.is_(None)
).count()
# 获取最新图书
latest_books = Book.query.filter_by(status=1).order_by(Book.created_at.desc()).limit(4).all()
# 获取热门图书(根据借阅次数)
try:
# 这里假设你的数据库中有表记录借阅次数
popular_books_query = db.session.query(
Book, func.count(BorrowRecord.id).label('borrow_count')
).join(
BorrowRecord, Book.id == BorrowRecord.book_id, isouter=True
).filter(
Book.status == 1
).group_by(
Book.id
).order_by(
desc('borrow_count')
).limit(5)
# 提取图书对象并添加借阅计数
popular_books = []
for book, count in popular_books_query:
book.borrow_count = count
popular_books.append(book)
except Exception as e:
# 如果查询有问题,使用最新的书作为备选
popular_books = latest_books.copy() if latest_books else []
print(f"获取热门图书失败: {str(e)}")
# 获取最新公告
announcements = Announcement.query.filter_by(status=1).order_by(
Announcement.is_top.desc(),
Announcement.created_at.desc()
).limit(3).all()
now = datetime.now()
# 获取用户的未读通知
user_notifications = []
if current_user.is_authenticated:
user_notifications = Notification.query.filter_by(
user_id=current_user.id,
status=0
).order_by(
Notification.created_at.desc()
).limit(5).all()
return render_template('index.html',
stats=stats,
latest_books=latest_books,
popular_books=popular_books,
announcements=announcements,
user_notifications=user_notifications,
now=now
)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.template_filter('nl2br')
def nl2br_filter(s):
if s:
return Markup(s.replace('\n', '
'))
return s
@app.context_processor
def utility_processor():
def get_unread_notifications_count(user_id):
if user_id:
return Notification.get_unread_count(user_id)
return 0
def get_recent_notifications(user_id, limit=5):
if user_id:
# 按时间倒序获取最近的几条通知
notifications = Notification.query.filter_by(user_id=user_id) \
.order_by(Notification.created_at.desc()) \
.limit(limit) \
.all()
return notifications
return []
return dict(
get_unread_notifications_count=get_unread_notifications_count,
get_recent_notifications=get_recent_notifications
)
@app.context_processor
def inject_now():
return {'now': datetime.now()}
return app
================================================================================
File: ./app/init_permissions.py
================================================================================
from app import create_app
from app.models.database import db
from app.models.user import Role
from app.models.permission import Permission
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def init_permissions():
"""初始化系统权限"""
logger.info("开始初始化系统权限...")
# 只定义管理类权限,对应现有的 @admin_required 装饰的路由
permissions = [
# 公告管理权限
{'code': 'manage_announcements', 'name': '公告管理', 'description': '允许管理系统公告(发布、编辑、删除、置顶等)'},
# 图书管理权限
{'code': 'manage_books', 'name': '图书管理', 'description': '允许管理图书(添加、编辑、删除图书)'},
{'code': 'manage_categories', 'name': '分类管理', 'description': '允许管理图书分类'},
{'code': 'import_export_books', 'name': '导入导出图书', 'description': '允许批量导入和导出图书数据'},
# 借阅管理权限
{'code': 'manage_borrows', 'name': '借阅管理', 'description': '允许管理全系统借阅记录和处理借还书操作'},
{'code': 'manage_overdue', 'name': '逾期管理', 'description': '允许查看和处理逾期借阅'},
# 库存管理权限
{'code': 'manage_inventory', 'name': '库存管理', 'description': '允许查看和调整图书库存'},
# 日志权限
{'code': 'view_logs', 'name': '查看日志', 'description': '允许查看系统操作日志'},
# 统计权限
{'code': 'view_statistics', 'name': '查看统计', 'description': '允许查看统计分析数据'},
# 用户管理权限
{'code': 'manage_users', 'name': '用户管理', 'description': '允许管理用户(添加、编辑、禁用、删除用户)'},
{'code': 'manage_roles', 'name': '角色管理', 'description': '允许管理角色和权限'},
]
# 添加权限记录
added_count = 0
updated_count = 0
for perm_data in permissions:
# 检查权限是否已存在
existing_perm = Permission.query.filter_by(code=perm_data['code']).first()
if existing_perm:
# 更新现有权限信息
existing_perm.name = perm_data['name']
existing_perm.description = perm_data['description']
updated_count += 1
else:
# 创建新权限
permission = Permission(**perm_data)
db.session.add(permission)
added_count += 1
# 提交所有权限
db.session.commit()
logger.info(f"权限初始化完成: 新增 {added_count} 个, 更新 {updated_count} 个")
# 处理角色权限分配
assign_role_permissions()
def assign_role_permissions():
"""为系统默认角色分配权限"""
logger.info("开始分配角色权限...")
# 获取所有权限
all_permissions = Permission.query.all()
# 获取系统内置角色
admin_role = Role.query.get(1) # 管理员角色
user_role = Role.query.get(2) # 普通用户角色
if admin_role and user_role:
# 管理员拥有所有权限
admin_role.permissions = all_permissions
# 普通用户无需特殊管理权限
user_role.permissions = []
db.session.commit()
logger.info(f"管理员角色分配了 {len(all_permissions)} 个权限")
logger.info(f"普通用户角色无管理权限")
else:
logger.error("无法找到内置角色,请确保角色表已正确初始化")
def main():
"""主函数"""
app = create_app()
with app.app_context():
init_permissions()
if __name__ == "__main__":
main()
================================================================================
File: ./app/utils/auth.py
================================================================================
from functools import wraps
from flask import redirect, url_for, flash, request
from flask_login import current_user
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
print(f"DEBUG: login_required 检查 - current_user.is_authenticated = {current_user.is_authenticated}")
if not current_user.is_authenticated:
flash('请先登录', 'warning')
return redirect(url_for('user.login', next=request.url))
return f(*args, **kwargs)
return decorated_function
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
print(f"DEBUG: admin_required 检查 - current_user.is_authenticated = {current_user.is_authenticated}")
if not current_user.is_authenticated:
flash('请先登录', 'warning')
return redirect(url_for('user.login', next=request.url))
print(f"DEBUG: admin_required 检查 - current_user.role_id = {getattr(current_user, 'role_id', None)}")
if getattr(current_user, 'role_id', None) != 1: # 安全地获取role_id属性
flash('权限不足', 'danger')
return redirect(url_for('index'))
return f(*args, **kwargs)
return decorated_function
def permission_required(permission_code):
"""
检查用户是否拥有特定权限的装饰器
:param permission_code: 权限代码,例如 'manage_books'
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
print(
f"DEBUG: permission_required({permission_code}) 检查 - current_user.is_authenticated = {current_user.is_authenticated}")
# 检查用户是否登录
if not current_user.is_authenticated:
flash('请先登录', 'warning')
return redirect(url_for('user.login', next=request.url))
# 管理员拥有所有权限
if getattr(current_user, 'role_id', None) == 1:
return f(*args, **kwargs)
# 获取用户角色并检查是否有指定权限
from app.models.user import Role
role = Role.query.get(current_user.role_id)
if not role:
flash('用户角色异常', 'danger')
return redirect(url_for('index'))
# 检查角色是否有指定权限
has_permission = False
for perm in role.permissions:
if perm.code == permission_code:
has_permission = True
break
if not has_permission:
print(f"DEBUG: 用户 {current_user.username} 缺少权限 {permission_code}")
flash('您没有执行此操作的权限', 'danger')
return redirect(url_for('index'))
return f(*args, **kwargs)
return decorated_function
return decorator
================================================================================
File: ./app/utils/db.py
================================================================================
================================================================================
File: ./app/utils/__init__.py
================================================================================
================================================================================
File: ./app/utils/logger.py
================================================================================
from flask import request, current_app
from flask_login import current_user
from app.models.log import Log
def record_activity(action, target_type=None, target_id=None, description=None):
"""
记录用户活动
参数:
- action: 操作类型,如 'login', 'logout', 'create', 'update', 'delete', 'borrow', 'return' 等
- target_type: 操作对象类型,如 'book', 'user', 'borrow' 等
- target_id: 操作对象ID
- description: 操作详细描述
"""
try:
# 获取当前用户ID
user_id = current_user.id if current_user.is_authenticated else None
# 获取客户端IP地址
ip_address = request.remote_addr
if 'X-Forwarded-For' in request.headers:
ip_address = request.headers.getlist("X-Forwarded-For")[0].rpartition(' ')[-1]
# 记录日志
Log.add_log(
action=action,
user_id=user_id,
target_type=target_type,
target_id=target_id,
ip_address=ip_address,
description=description
)
return True
except Exception as e:
# 记录错误,但不影响主要功能
if current_app:
current_app.logger.error(f"Error recording activity log: {str(e)}")
return False
================================================================================
File: ./app/utils/template_helpers.py
================================================================================
from app.models.permission import Permission
from flask import current_app
def register_template_helpers(app):
@app.context_processor
def inject_permissions():
def has_permission(user, permission_code):
"""检查用户是否拥有指定权限"""
if not user or not user.is_authenticated:
return False
# 管理员拥有所有权限
if user.role_id == 1:
return True
# 检查用户角色权限
if user.role:
for perm in user.role.permissions:
if perm.code == permission_code:
return True
return False
return dict(has_permission=has_permission)
# 在 create_app 函数中调用
# register_template_helpers(app)
================================================================================
File: ./app/utils/email.py
================================================================================
import smtplib
import random
import string
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from flask import current_app
import logging
# 配置日志
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# 配置邮件发送功能
def send_verification_email(to_email, verification_code):
"""
发送验证码邮件
"""
try:
# 从应用配置获取邮件设置
email_host = current_app.config['EMAIL_HOST']
email_port = current_app.config['EMAIL_PORT']
email_username = current_app.config['EMAIL_USERNAME']
email_password = current_app.config['EMAIL_PASSWORD']
email_from = current_app.config['EMAIL_FROM']
email_from_name = current_app.config['EMAIL_FROM_NAME']
logger.info(f"准备发送邮件到: {to_email}, 验证码: {verification_code}")
logger.debug(f"邮件配置: 主机={email_host}, 端口={email_port}")
# 邮件内容
msg = MIMEMultipart()
msg['From'] = f"{email_from_name} <{email_from}>"
msg['To'] = to_email
msg['Subject'] = "图书管理系统 - 验证码"
# 邮件正文
body = f"""
图书管理系统 - 邮箱验证
您好,
感谢您注册图书管理系统,您的验证码是:
{verification_code}
该验证码将在10分钟内有效,请勿将验证码分享给他人。
如果您没有请求此验证码,请忽略此邮件。
此邮件为系统自动发送,请勿回复。
© 2025 图书管理系统
"""
msg.attach(MIMEText(body, 'html'))
logger.debug("尝试连接到SMTP服务器...")
# 连接服务器发送邮件
server = smtplib.SMTP(email_host, email_port)
server.set_debuglevel(1) # 启用详细的SMTP调试输出
logger.debug("检查是否需要STARTTLS加密...")
if current_app.config.get('EMAIL_ENCRYPTION') == 'starttls':
logger.debug("启用STARTTLS...")
server.starttls()
logger.debug(f"尝试登录邮箱: {email_username}")
server.login(email_username, email_password)
logger.debug("发送邮件...")
server.send_message(msg)
logger.debug("关闭连接...")
server.quit()
logger.info(f"邮件发送成功: {to_email}")
return True
except Exception as e:
logger.error(f"邮件发送失败: {str(e)}", exc_info=True)
return False
def generate_verification_code(length=6):
"""
生成数字验证码
"""
return ''.join(random.choice(string.digits) for _ in range(length))
================================================================================
File: ./app/utils/helpers.py
================================================================================
================================================================================
File: ./app/models/user.py
================================================================================
from app.models.database import db
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from flask_login import UserMixin
from app.models.permission import RolePermission, Permission
#db = SQLAlchemy()
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(64), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
email = db.Column(db.String(128), unique=True, nullable=True)
phone = db.Column(db.String(20), unique=True, nullable=True)
nickname = db.Column(db.String(64), nullable=True)
status = db.Column(db.Integer, default=1) # 1: active, 0: disabled
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), default=2) # 2: 普通用户, 1: 管理员
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, 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):
return self.status == 1
def set_password(self, password):
"""设置密码,使用哈希加密"""
self.password = generate_password_hash(password)
def check_password(self, password):
"""验证密码"""
return check_password_hash(self.password, password)
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'username': self.username,
'email': self.email,
'phone': self.phone,
'nickname': self.nickname,
'status': self.status,
'role_id': self.role_id,
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S')
}
@classmethod
def create_user(cls, username, password, email=None, phone=None, nickname=None, role_id=2):
"""创建新用户"""
user = User(
username=username,
password=password,
email=email,
phone=phone,
nickname=nickname,
role_id=role_id
)
db.session.add(user)
db.session.commit()
return user
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
role_name = db.Column(db.String(32), unique=True, nullable=False)
description = db.Column(db.String(128))
permissions = db.relationship(
'Permission',
secondary='role_permissions',
backref=db.backref('roles', lazy='dynamic'),
lazy='dynamic'
)
users = db.relationship('User', backref='role')
================================================================================
File: ./app/models/permission.py
================================================================================
from app.models.database import db
from datetime import datetime
# 这是权限表 model
class Permission(db.Model):
__tablename__ = 'permissions'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(64), unique=True, nullable=False, comment='权限代码,用于系统识别')
name = db.Column(db.String(64), nullable=False, comment='权限名称,用于界面显示')
description = db.Column(db.String(255), comment='权限描述,说明权限用途')
# 角色-权限 关联表(辅助对象模式,方便ORM关系管理)
class RolePermission(db.Model):
__tablename__ = 'role_permissions'
role_id = db.Column(db.Integer, db.ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True, comment='角色ID,关联roles表')
permission_id = db.Column(db.Integer, db.ForeignKey('permissions.id', ondelete='CASCADE'), primary_key=True, comment='权限ID,关联permissions表')
created_at = db.Column(db.DateTime, default=datetime.now, comment='权限分配时间')
================================================================================
File: ./app/models/log.py
================================================================================
from datetime import datetime
from app.models.user import db, User # 从user模块导入db,而不是从utils导入
class Log(db.Model):
__tablename__ = 'logs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
action = db.Column(db.String(64), nullable=False)
target_type = db.Column(db.String(32), nullable=True)
target_id = db.Column(db.Integer, nullable=True)
ip_address = db.Column(db.String(45), nullable=True)
description = db.Column(db.String(255), nullable=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
# 关联用户
user = db.relationship('User', backref=db.backref('logs', lazy=True))
def __init__(self, action, user_id=None, target_type=None, target_id=None,
ip_address=None, description=None):
self.user_id = user_id
self.action = action
self.target_type = target_type
self.target_id = target_id
self.ip_address = ip_address
self.description = description
self.created_at = datetime.now()
@staticmethod
def add_log(action, user_id=None, target_type=None, target_id=None,
ip_address=None, description=None):
"""添加一条日志记录"""
try:
log = Log(
action=action,
user_id=user_id,
target_type=target_type,
target_id=target_id,
ip_address=ip_address,
description=description
)
db.session.add(log)
db.session.commit()
return True, "日志记录成功"
except Exception as e:
db.session.rollback()
return False, f"日志记录失败: {str(e)}"
@staticmethod
def get_logs(page=1, per_page=20, user_id=None, action=None,
target_type=None, start_date=None, end_date=None):
"""查询日志记录"""
query = Log.query.order_by(Log.created_at.desc())
if user_id:
query = query.filter(Log.user_id == user_id)
if action:
query = query.filter(Log.action == action)
if target_type:
query = query.filter(Log.target_type == target_type)
if start_date:
query = query.filter(Log.created_at >= start_date)
if end_date:
query = query.filter(Log.created_at <= end_date)
return query.paginate(page=page, per_page=per_page)
================================================================================
File: ./app/models/notification.py
================================================================================
from datetime import datetime
from app.models.user import db, User # 从user模块导入db,而不是从app.models导入
class Notification(db.Model):
__tablename__ = 'notifications'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
title = db.Column(db.String(128), nullable=False)
content = db.Column(db.Text, nullable=False)
type = db.Column(db.String(32), nullable=False) # 通知类型:system, borrow, return, overdue, etc.
status = db.Column(db.Integer, default=0) # 0-未读, 1-已读
sender_id = db.Column(db.Integer, db.ForeignKey('users.id'))
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
read_at = db.Column(db.DateTime)
# 关联关系
user = db.relationship('User', foreign_keys=[user_id], backref='notifications')
sender = db.relationship('User', foreign_keys=[sender_id], backref='sent_notifications')
def to_dict(self):
"""将通知转换为字典"""
return {
'id': self.id,
'user_id': self.user_id,
'title': self.title,
'content': self.content,
'type': self.type,
'status': self.status,
'sender_id': self.sender_id,
'sender_name': self.sender.username if self.sender else 'System',
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'read_at': self.read_at.strftime('%Y-%m-%d %H:%M:%S') if self.read_at else None
}
@staticmethod
def get_user_notifications(user_id, page=1, per_page=10, unread_only=False):
"""获取用户通知"""
query = Notification.query.filter_by(user_id=user_id)
if unread_only:
query = query.filter_by(status=0)
return query.order_by(Notification.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
@staticmethod
def get_unread_count(user_id):
"""获取用户未读通知数量"""
return Notification.query.filter_by(user_id=user_id, status=0).count()
@staticmethod
def mark_as_read(notification_id, user_id=None):
"""将通知标记为已读"""
notification = Notification.query.get(notification_id)
if not notification:
return False, "通知不存在"
# 验证用户权限
if user_id and notification.user_id != user_id:
return False, "无权操作此通知"
notification.status = 1
notification.read_at = datetime.now()
try:
db.session.commit()
return True, "已标记为已读"
except Exception as e:
db.session.rollback()
return False, str(e)
@staticmethod
def create_notification(user_id, title, content, notification_type, sender_id=None):
"""创建新通知"""
notification = Notification(
user_id=user_id,
title=title,
content=content,
type=notification_type,
sender_id=sender_id
)
try:
db.session.add(notification)
db.session.commit()
return True, notification
except Exception as e:
db.session.rollback()
return False, str(e)
@staticmethod
def create_system_notification(user_ids, title, content, notification_type, sender_id=None):
"""创建系统通知,发送给多个用户"""
success_count = 0
fail_count = 0
for user_id in user_ids:
success, _ = Notification.create_notification(
user_id=user_id,
title=title,
content=content,
notification_type=notification_type,
sender_id=sender_id
)
if success:
success_count += 1
else:
fail_count += 1
return success_count, fail_count
================================================================================
File: ./app/models/database.py
================================================================================
from flask_sqlalchemy import SQLAlchemy
# 创建共享的SQLAlchemy实例
db = SQLAlchemy()
================================================================================
File: ./app/models/__init__.py
================================================================================
def create_app():
app = Flask(__name__)
# ... 配置代码 ...
# 初始化数据库
db.init_app(app)
# 导入模型,确保所有模型在创建表之前被加载
from app.models.user import User, Role
from app.models.book import Book, Category
from app.models.borrow import BorrowRecord
from app.models.inventory import InventoryLog
# 创建数据库表
with app.app_context():
db.create_all()
# ... 其余代码 ...
================================================================================
File: ./app/models/book.py
================================================================================
from app.models.user import db
from datetime import datetime
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=True)
sort = db.Column(db.Integer, default=0)
# 关系 - 只保留与自身的关系
parent = db.relationship('Category', remote_side=[id], backref='children')
def __repr__(self):
return f''
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
author = db.Column(db.String(128), nullable=False)
publisher = db.Column(db.String(128), nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=True)
tags = db.Column(db.String(255), nullable=True)
isbn = db.Column(db.String(32), unique=True, nullable=True)
publish_year = db.Column(db.String(16), nullable=True)
description = db.Column(db.Text, nullable=True)
cover_url = db.Column(db.String(255), nullable=True)
stock = db.Column(db.Integer, default=0)
price = db.Column(db.Numeric(10, 2), nullable=True)
status = db.Column(db.Integer, default=1) # 1:可用, 0:不可用
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''
================================================================================
File: ./app/models/borrow.py
================================================================================
from app.models.user import db
from datetime import datetime
class BorrowRecord(db.Model):
__tablename__ = 'borrow_records'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
book_id = db.Column(db.Integer, db.ForeignKey('books.id'), nullable=False)
borrow_date = db.Column(db.DateTime, nullable=False, default=datetime.now)
due_date = db.Column(db.DateTime, nullable=False)
return_date = db.Column(db.DateTime, nullable=True)
renew_count = db.Column(db.Integer, default=0)
status = db.Column(db.Integer, default=1) # 1: 借出, 0: 已归还
remark = db.Column(db.String(255), nullable=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
# 添加反向关系引用
user = db.relationship('User', backref=db.backref('borrow_records', lazy='dynamic'))
book = db.relationship('Book', backref=db.backref('borrow_records', lazy='dynamic'))
# book 关系会在后面步骤添加
def __repr__(self):
return f''
================================================================================
File: ./app/models/announcement.py
================================================================================
from datetime import datetime
from app.models.user import db, User # 从user模块导入db,而不是从app.models导入
class Announcement(db.Model):
__tablename__ = 'announcements'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128), nullable=False)
content = db.Column(db.Text, nullable=False)
publisher_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
is_top = db.Column(db.Boolean, default=False)
status = db.Column(db.Integer, default=1) # 1-正常, 0-已下架
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
# 关联关系
publisher = db.relationship('User', backref='announcements')
def to_dict(self):
"""将公告转换为字典"""
return {
'id': self.id,
'title': self.title,
'content': self.content,
'publisher_id': self.publisher_id,
'publisher_name': self.publisher.username if self.publisher else '',
'is_top': self.is_top,
'status': self.status,
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S')
}
@staticmethod
def get_active_announcements(limit=None):
"""获取活跃的公告"""
query = Announcement.query.filter_by(status=1).order_by(
Announcement.is_top.desc(),
Announcement.created_at.desc()
)
if limit:
query = query.limit(limit)
return query.all()
@staticmethod
def get_announcement_by_id(announcement_id):
"""根据ID获取公告"""
return Announcement.query.get(announcement_id)
@staticmethod
def create_announcement(title, content, publisher_id, is_top=False):
"""创建新公告"""
announcement = Announcement(
title=title,
content=content,
publisher_id=publisher_id,
is_top=is_top
)
try:
db.session.add(announcement)
db.session.commit()
return True, announcement
except Exception as e:
db.session.rollback()
return False, str(e)
@staticmethod
def update_announcement(announcement_id, title, content, is_top=None):
"""更新公告内容"""
announcement = Announcement.query.get(announcement_id)
if not announcement:
return False, "公告不存在"
announcement.title = title
announcement.content = content
if is_top is not None:
announcement.is_top = is_top
try:
db.session.commit()
return True, announcement
except Exception as e:
db.session.rollback()
return False, str(e)
@staticmethod
def change_status(announcement_id, status):
"""更改公告状态"""
announcement = Announcement.query.get(announcement_id)
if not announcement:
return False, "公告不存在"
announcement.status = status
try:
db.session.commit()
return True, "状态已更新"
except Exception as e:
db.session.rollback()
return False, str(e)
@staticmethod
def change_top_status(announcement_id, is_top):
"""更改置顶状态"""
announcement = Announcement.query.get(announcement_id)
if not announcement:
return False, "公告不存在"
announcement.is_top = is_top
try:
db.session.commit()
return True, "置顶状态已更新"
except Exception as e:
db.session.rollback()
return False, str(e)
================================================================================
File: ./app/models/inventory.py
================================================================================
from app.models.user import db
from datetime import datetime
class InventoryLog(db.Model):
__tablename__ = 'inventory_logs'
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey('books.id'), nullable=False)
change_type = db.Column(db.String(32), nullable=False) # 'in' 入库, 'out' 出库
change_amount = db.Column(db.Integer, nullable=False)
after_stock = db.Column(db.Integer, nullable=False)
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
remark = db.Column(db.String(255), nullable=True)
changed_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
# 添加反向关系引用
operator = db.relationship('User', backref=db.backref('inventory_logs', lazy='dynamic'))
# book 关系会在后面步骤添加
def __repr__(self):
return f''
================================================================================
File: ./app/static/css/log-detail.css
================================================================================
/* 日志详情样式 */
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.content-header h1 {
margin: 0;
font-size: 24px;
}
.log-info {
padding: 10px;
}
.info-item {
margin-bottom: 15px;
display: flex;
}
.info-item .label {
width: 100px;
font-weight: 600;
color: #495057;
}
.info-item .value {
flex: 1;
}
.description {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
display: block;
}
.description .label {
display: block;
width: 100%;
margin-bottom: 10px;
}
.description .value {
display: block;
width: 100%;
white-space: pre-wrap;
word-break: break-word;
}
================================================================================
File: ./app/static/css/register.css
================================================================================
/* register.css - 注册页面专用样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
:root {
--primary-color: #4a89dc;
--primary-hover: #3b78c4;
--secondary-color: #5cb85c;
--text-color: #333;
--light-text: #666;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #ddd;
--error-color: #e74c3c;
--success-color: #2ecc71;
}
body.dark-mode {
--primary-color: #5a9aed;
--primary-hover: #4a89dc;
--secondary-color: #6bc76b;
--text-color: #f1f1f1;
--light-text: #aaa;
--bg-color: #1a1a1a;
--card-bg: #2c2c2c;
--border-color: #444;
}
body {
background-color: var(--bg-color);
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
min-height: 100vh;
color: var(--text-color);
transition: all 0.3s ease;
}
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
cursor: pointer;
padding: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.overlay {
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
.main-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 20px;
}
.login-container {
background-color: var(--card-bg);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
width: 450px;
padding: 35px;
position: relative;
overflow: hidden;
animation: fadeIn 0.5s ease;
}
.register-container {
width: 500px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.logo {
text-align: center;
margin-bottom: 25px;
position: relative;
}
.logo img {
width: 90px;
height: 90px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 5px;
background-color: #fff;
transition: transform 0.3s ease;
}
h1 {
text-align: center;
color: var(--text-color);
margin-bottom: 10px;
font-weight: 600;
font-size: 28px;
}
.subtitle {
text-align: center;
color: var(--light-text);
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-color);
font-weight: 500;
font-size: 14px;
}
.input-with-icon {
position: relative;
}
.input-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--light-text);
}
.form-control {
width: 100%;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px 0 45px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.2);
outline: none;
}
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: var(--light-text);
}
.validation-message {
margin-top: 6px;
font-size: 12px;
color: var(--error-color);
display: none;
}
.validation-message.show {
display: block;
animation: shake 0.5s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.btn-login {
width: 100%;
height: 48px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-login:hover {
background-color: var(--primary-hover);
}
.btn-login:active {
transform: scale(0.98);
}
.btn-login .loading {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.btn-login.loading-state {
color: transparent;
}
.btn-login.loading-state .loading {
display: block;
}
.signup {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: var(--light-text);
}
.signup a {
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.signup a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.alert {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
color: #721c24;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
.verification-code-container {
display: flex;
gap: 10px;
}
.verification-input {
flex: 1;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.send-code-btn {
padding: 0 15px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
transition: all 0.3s ease;
}
.send-code-btn:hover {
background-color: var(--primary-hover);
}
.send-code-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
}
footer a {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
}
@media (max-width: 576px) {
.login-container, .register-container {
width: 100%;
padding: 25px;
border-radius: 0;
}
.theme-toggle {
top: 10px;
}
.logo img {
width: 70px;
height: 70px;
}
h1 {
font-size: 22px;
}
.main-container {
padding: 0;
}
.verification-code-container {
flex-direction: column;
}
}
================================================================================
File: ./app/static/css/inventory-book-logs.css
================================================================================
/* 冰雪奇缘主题库存日志页面样式 */
/* 基础背景与字体 */
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%;
}
}
================================================================================
File: ./app/static/css/user-list.css
================================================================================
/* 用户列表页面样式 */
.user-list-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 页面标题和操作按钮 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.page-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}
.page-header .actions {
display: flex;
gap: 10px;
}
/* 搜索和筛选区域 */
.search-filter-container {
margin-bottom: 20px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 6px;
}
.search-filter-form .form-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 15px;
}
.search-box {
position: relative;
flex: 1;
min-width: 250px;
}
.search-box input {
padding-right: 40px;
border-radius: 4px;
border: 1px solid #ddd;
}
.btn-search {
position: absolute;
right: 5px;
top: 5px;
background: none;
border: none;
color: #666;
}
.filter-box {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-box select {
min-width: 120px;
border-radius: 4px;
border: 1px solid #ddd;
padding: 5px 10px;
}
.btn-filter, .btn-reset {
padding: 6px 15px;
border-radius: 4px;
}
.btn-filter {
background-color: #4c84ff;
color: white;
border: none;
}
.btn-reset {
background-color: #f8f9fa;
color: #333;
border: 1px solid #ddd;
}
/* 表格样式 */
.table {
width: 100%;
margin-bottom: 0;
color: #333;
border-collapse: collapse;
}
.table th {
background-color: #f8f9fa;
padding: 12px 15px;
font-weight: 600;
text-align: left;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
}
.table td {
padding: 12px 15px;
vertical-align: middle;
border-bottom: 1px solid #f0f0f0;
}
.table tr:hover {
background-color: #f8f9fa;
}
/* 状态标签 */
.status-badge {
display: inline-block;
padding: 5px 10px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
.status-badge.active {
background-color: #e8f5e9;
color: #43a047;
}
.status-badge.inactive {
background-color: #ffebee;
color: #e53935;
}
/* 操作按钮 */
.actions {
display: flex;
gap: 5px;
align-items: center;
}
.actions .btn {
padding: 5px 8px;
line-height: 1;
}
/* 分页控件 */
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
}
.pagination {
display: flex;
padding-left: 0;
list-style: none;
border-radius: 0.25rem;
}
.page-item {
margin: 0 2px;
}
.page-link {
position: relative;
display: block;
padding: 0.5rem 0.75rem;
margin-left: -1px;
color: #4c84ff;
background-color: #fff;
border: 1px solid #dee2e6;
text-decoration: none;
}
.page-item.active .page-link {
z-index: 3;
color: #fff;
background-color: #4c84ff;
border-color: #4c84ff;
}
.page-item.disabled .page-link {
color: #aaa;
pointer-events: none;
background-color: #f8f9fa;
border-color: #dee2e6;
}
/* 通知样式 */
.alert-box {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
}
.alert-box .alert {
margin-bottom: 10px;
padding: 10px 15px;
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.alert-box .fade-in {
opacity: 1;
}
.alert-box .fade-out {
opacity: 0;
}
/* 响应式调整 */
@media (max-width: 992px) {
.search-filter-form .form-row {
flex-direction: column;
}
.search-box, .filter-box {
width: 100%;
}
}
@media (max-width: 768px) {
.table {
display: block;
overflow-x: auto;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
================================================================================
File: ./app/static/css/book_ranking.css
================================================================================
/* app/static/css/book_ranking.css */
.table-container {
margin-top: 30px;
}
.table-container h3 {
text-align: center;
margin-bottom: 20px;
color: var(--accent-color);
font-family: 'Ma Shan Zheng', cursive, Arial, sans-serif;
font-size: 1.6em;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
}
.table-container h3:before,
.table-container h3:after {
content: '';
position: absolute;
height: 2px;
background: linear-gradient(to right, transparent, var(--primary-color), transparent);
width: 120px;
top: 50%;
}
.table-container h3:before {
right: 100%;
margin-right: 15px;
}
.table-container h3:after {
left: 100%;
margin-left: 15px;
}
.data-table img {
width: 55px;
height: 80px;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 2px solid white;
}
.data-table tr:hover img {
transform: scale(1.08);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
border-color: var(--primary-color);
}
.data-table .rank {
font-weight: 700;
text-align: center;
position: relative;
}
/* 前三名特殊样式 */
.data-table tr:nth-child(1) .rank:before {
content: '👑';
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
}
.data-table tr:nth-child(2) .rank:before {
content: '✨';
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
font-size: 16px;
}
.data-table tr:nth-child(3) .rank:before {
content: '🌟';
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
font-size: 16px;
}
.data-table .book-title {
font-weight: 500;
color: var(--accent-color);
transition: color 0.3s;
}
.data-table tr:hover .book-title {
color: #d06b9c;
}
.data-table .author {
font-style: italic;
color: var(--light-text);
}
.data-table .borrow-count {
font-weight: 600;
color: var(--accent-color);
position: relative;
display: block; /* 修改为block以占据整个单元格 */
text-align: center; /* 确保文本居中 */
}
.data-table .borrow-count:after {
content: '❤️';
font-size: 12px;
margin-left: 5px;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: translateY(5px);
display: inline-block;
}
.data-table tr:hover .borrow-count:after {
opacity: 1;
transform: translateY(0);
}
.no-data {
text-align: center;
padding: 40px;
color: var(--light-text);
background-color: var(--secondary-color);
border-radius: 12px;
font-style: italic;
border: 1px dashed var(--border-color);
}
/* 书籍行动画 */
#ranking-table-body tr {
transition: transform 0.3s ease, opacity 0.3s ease;
}
#ranking-table-body tr:hover {
transform: translateX(5px);
}
/* 加载动画美化 */
.loading-row td {
background-color: var(--secondary-color);
color: var(--accent-color);
font-size: 16px;
}
/* 书名悬停效果 */
.book-title {
position: relative;
text-decoration: none;
display: inline-block;
}
.book-title:after {
content: '';
position: absolute;
width: 100%;
height: 2px;
bottom: -2px;
left: 0;
background-color: var(--accent-color);
transform: scaleX(0);
transform-origin: bottom right;
transition: transform 0.3s ease-out;
}
tr:hover .book-title:after {
transform: scaleX(1);
transform-origin: bottom left;
}
/* 特殊效果:波浪下划线 */
@keyframes wave {
0%, 100% { background-position-x: 0%; }
50% { background-position-x: 100%; }
}
.page-title:after {
content: '';
display: block;
width: 100px;
height: 5px;
margin: 10px auto 0;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color), var(--primary-color));
background-size: 200% 100%;
border-radius: 5px;
animation: wave 3s infinite linear;
}
.book-list-title {
text-align: center;
margin-bottom: 25px;
color: var(--accent-color);
font-family: 'Ma Shan Zheng', cursive, Arial, sans-serif;
font-size: 1.6em;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
padding: 0 15px;
}
.book-icon {
font-size: 0.9em;
margin: 0 8px;
opacity: 0.85;
}
.column-icon {
font-size: 0.9em;
margin-right: 5px;
opacity: 0.8;
}
.book-list-title:before,
.book-list-title:after {
content: '';
position: absolute;
height: 2px;
background: linear-gradient(to right, transparent, var(--primary-color), transparent);
width: 80px;
top: 50%;
}
.book-list-title:before {
right: 100%;
margin-right: 15px;
}
.book-list-title:after {
left: 100%;
margin-left: 15px;
}
/* 表格中的图标样式 */
.data-table .borrow-count:after {
content: '📚';
font-size: 12px;
margin-left: 5px;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: translateY(5px);
display: inline-block;
}
/* 前三名特殊样式 - 替换这部分代码 */
.data-table tr:nth-child(1) .rank:before,
.data-table tr:nth-child(2) .rank:before,
.data-table tr:nth-child(3) .rank:before {
position: absolute;
left: 10px; /* 调整到数字左侧 */
top: 50%; /* 垂直居中 */
transform: translateY(-50%); /* 保持垂直居中 */
opacity: 0.9;
}
/* 分别设置每个奖牌的内容 */
.data-table tr:nth-child(1) .rank:before {
content: '🏆';
font-size: 18px;
}
.data-table tr:nth-child(2) .rank:before {
content: '🥈';
font-size: 16px;
}
.data-table tr:nth-child(3) .rank:before {
content: '🥉';
font-size: 16px;
}
/* 调整排名单元格的内边距,为图标留出空间 */
.data-table .rank {
padding-left: 35px; /* 增加左内边距为图标腾出空间 */
text-align: left; /* 使数字左对齐 */
}
/* 加载动画美化 */
.loading-animation {
display: flex;
align-items: center;
justify-content: center;
}
.loading-animation:before {
content: '📖';
margin-right: 10px;
animation: bookFlip 2s infinite;
display: inline-block;
}
@keyframes bookFlip {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(180deg); }
100% { transform: rotateY(360deg); }
}
================================================================================
File: ./app/static/css/book-detail.css
================================================================================
/* 图书详情页样式 */
.book-detail-container {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.actions {
display: flex;
gap: 10px;
}
.book-content {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
overflow: hidden;
}
.book-header {
display: flex;
padding: 25px;
border-bottom: 1px solid #f0f0f0;
background-color: #f9f9f9;
}
.book-cover-large {
flex: 0 0 200px;
height: 300px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
margin-right: 30px;
}
.book-cover-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
.no-cover-large {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #aaa;
}
.no-cover-large i {
font-size: 48px;
margin-bottom: 10px;
}
.book-main-info {
flex: 1;
}
.book-title {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 15px;
color: #333;
}
.book-author {
font-size: 1.1rem;
color: #555;
margin-bottom: 20px;
}
.book-meta-info {
margin-bottom: 25px;
}
.meta-item {
display: flex;
align-items: center;
margin-bottom: 12px;
color: #666;
}
.meta-item i {
width: 20px;
margin-right: 10px;
text-align: center;
color: #555;
}
.meta-value {
font-weight: 500;
color: #444;
}
.tag {
display: inline-block;
background-color: #e9ecef;
color: #495057;
padding: 2px 8px;
border-radius: 3px;
margin-right: 5px;
margin-bottom: 5px;
font-size: 0.85rem;
}
.book-status-info {
display: flex;
align-items: center;
gap: 20px;
margin-top: 20px;
}
.status-badge {
display: inline-block;
padding: 8px 16px;
border-radius: 4px;
font-weight: 600;
font-size: 0.9rem;
}
.status-badge.available {
background-color: #d4edda;
color: #155724;
}
.status-badge.unavailable {
background-color: #f8d7da;
color: #721c24;
}
.stock-info {
font-size: 0.95rem;
color: #555;
}
.book-details-section {
padding: 25px;
}
.book-details-section h3 {
font-size: 1.3rem;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
color: #444;
}
.book-description {
color: #555;
line-height: 1.6;
}
.no-description {
color: #888;
font-style: italic;
}
.book-borrow-history {
padding: 0 25px 25px;
}
.book-borrow-history h3 {
font-size: 1.3rem;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
color: #444;
}
.borrow-table {
border: 1px solid #eee;
}
.no-records {
color: #888;
font-style: italic;
text-align: center;
padding: 20px;
background-color: #f9f9f9;
border-radius: 4px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.book-header {
flex-direction: column;
}
.book-cover-large {
margin-right: 0;
margin-bottom: 20px;
max-width: 200px;
align-self: center;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.actions {
width: 100%;
}
}
================================================================================
File: ./app/static/css/announcement-form.css
================================================================================
.announcement-form-container {
padding: 20px;
max-width: 900px;
margin: 0 auto;
}
.page-header {
margin-bottom: 25px;
border-bottom: 1px solid #e3e3e3;
padding-bottom: 10px;
}
.card {
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
margin-bottom: 30px;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
font-weight: 500;
margin-bottom: 0.5rem;
display: block;
}
.ql-container {
min-height: 200px;
font-size: 16px;
}
.form-check {
margin-top: 20px;
margin-bottom: 20px;
}
.form-buttons {
display: flex;
justify-content: flex-end;
gap: 15px;
margin-top: 30px;
}
.form-buttons .btn {
min-width: 100px;
}
/* Quill编辑器样式重写 */
.ql-toolbar.ql-snow {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.ql-container.ql-snow {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
================================================================================
File: ./app/static/css/book.css
================================================================================
/* 图书列表页面样式 - 女性友好版 */
/* 背景和泡泡动画 */
.book-list-container {
padding: 24px;
background-color: #ffeef2; /* 淡粉色背景 */
min-height: calc(100vh - 60px);
position: relative;
overflow: hidden;
}
/* 泡泡动画 */
.book-list-container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 0;
}
@keyframes bubble {
0% {
transform: translateY(100%) scale(0);
opacity: 0;
}
50% {
opacity: 0.6;
}
100% {
transform: translateY(-100vh) scale(1);
opacity: 0;
}
}
.bubble {
position: absolute;
bottom: -50px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 50%;
z-index: 1;
animation: bubble 15s infinite ease-in;
}
/* 为页面添加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 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(233, 152, 174, 0.3);
position: relative;
z-index: 2;
}
.page-header h1 {
color: #d23f6e;
font-size: 1.9rem;
font-weight: 600;
margin: 0;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
}
/* 更漂亮的顶部按钮 */
.action-buttons {
display: flex;
gap: 12px;
position: relative;
z-index: 2;
}
.action-buttons .btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
border-radius: 50px;
font-weight: 500;
padding: 9px 18px;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.06);
border: none;
font-size: 0.95rem;
position: relative;
overflow: hidden;
}
.action-buttons .btn::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.2), transparent);
pointer-events: none;
}
.action-buttons .btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.08);
}
.action-buttons .btn:active {
transform: translateY(1px);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
/* 按钮颜色 */
.btn-primary {
background: linear-gradient(135deg, #5c88da, #4a73c7);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #56c596, #41b384);
color: white;
}
.btn-info {
background: linear-gradient(135deg, #5bc0de, #46b8da);
color: white;
}
.btn-secondary {
background: linear-gradient(135deg, #f0ad4e, #ec971f);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #ff7676, #ff5252);
color: white;
}
/* 过滤和搜索部分 */
.filter-section {
margin-bottom: 25px;
padding: 18px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 16px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
position: relative;
z-index: 2;
backdrop-filter: blur(5px);
}
.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;
}
.search-group .form-control {
border: 1px solid #f9c0d0;
border-right: none;
border-radius: 25px 0 0 25px;
padding: 10px 20px;
height: 42px;
font-size: 0.95rem;
background-color: rgba(255, 255, 255, 0.9);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
flex: 1;
}
.search-group .form-control:focus {
outline: none;
border-color: #e67e9f;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 0 0 3px rgba(230, 126, 159, 0.2);
}
.search-group .btn {
border-radius: 50%;
width: 42px;
height: 42px;
min-width: 42px;
padding: 0;
background: linear-gradient(135deg, #e67e9f 60%, #ffd3e1 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
margin-left: -1px; /* 防止和输入框间有缝隙 */
font-size: 1.1rem;
box-shadow: 0 2px 6px rgba(230, 126, 159, 0.10);
transition: background 0.2s, box-shadow 0.2s;
}
.search-group .btn:hover {
background: linear-gradient(135deg, #d23f6e 80%, #efb6c6 100%);
color: #fff;
box-shadow: 0 4px 12px rgba(230, 126, 159, 0.14);
}
.filter-row {
display: flex;
flex-wrap: wrap;
gap: 15px;
width: 100%;
}
.filter-group {
flex: 1;
min-width: 130px;
}
.filter-section .form-control {
border: 1px solid #f9c0d0;
border-radius: 25px;
height: 42px;
padding: 10px 20px;
background-color: rgba(255, 255, 255, 0.9);
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='%23e67e9f' 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;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
width: 100%;
}
.filter-section .form-control:focus {
outline: none;
border-color: #e67e9f;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 0 0 3px rgba(230, 126, 159, 0.2);
}
/* 图书网格布局 */
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
margin-bottom: 30px;
position: relative;
z-index: 2;
}
/* 图书卡片样式 */
.book-card {
display: flex;
flex-direction: column;
border-radius: 16px;
overflow: hidden;
background-color: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
height: 100%;
position: relative;
border: 1px solid rgba(233, 152, 174, 0.2);
}
.book-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.1);
}
.book-cover {
width: 100%;
height: 180px;
background-color: #faf3f5;
overflow: hidden;
position: relative;
}
.book-cover::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, transparent 60%, rgba(249, 219, 227, 0.4));
pointer-events: none;
}
.book-cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.book-card:hover .book-cover img {
transform: scale(1.05);
}
.no-cover {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #ffeef2 0%, #ffd9e2 100%);
color: #e67e9f;
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 1;
pointer-events: none;
}
.no-cover i {
font-size: 36px;
margin-bottom: 10px;
}
.book-info {
padding: 20px;
display: flex;
flex-direction: column;
flex: 1;
}
.book-title {
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 10px;
color: #d23f6e;
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: #888;
margin-bottom: 15px;
}
.book-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.book-category {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
background-color: #ffebf0;
color: #e67e9f;
font-weight: 500;
}
.book-status {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
}
.book-status.available {
background-color: #dffff6;
color: #26a69a;
}
.book-status.unavailable {
background-color: #ffeeee;
color: #e57373;
}
.book-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
font-size: 0.9rem;
color: #777;
}
.book-details p {
margin: 0;
display: flex;
}
.book-details strong {
min-width: 65px;
color: #999;
font-weight: 600;
}
/* 按钮组样式 */
.book-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: auto;
}
.book-actions .btn {
padding: 8px 0;
font-size: 0.9rem;
text-align: center;
border-radius: 25px;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
border: none;
font-weight: 500;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.book-actions .btn:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}
.book-actions .btn i {
font-size: 0.85rem;
}
/* 具体按钮颜色 */
.book-actions .btn-primary {
background: linear-gradient(135deg, #5c88da, #4a73c7);
}
.book-actions .btn-info {
background: linear-gradient(135deg, #5bc0de, #46b8da);
}
.book-actions .btn-success {
background: linear-gradient(135deg, #56c596, #41b384);
}
.book-actions .btn-danger {
background: linear-gradient(135deg, #ff7676, #ff5252);
}
/* 无图书状态 */
.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;
}
.no-books i {
font-size: 60px;
color: #f9c0d0;
margin-bottom: 20px;
}
.no-books p {
font-size: 1.1rem;
color: #e67e9f;
font-weight: 500;
}
/* 分页容器 */
.pagination-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 30px;
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: #777;
font-weight: 500;
transition: all 0.2s;
position: relative;
}
.pagination .page-link:hover {
color: #e67e9f;
background-color: #fff9fb;
}
.pagination .page-item.active .page-link {
background-color: #e67e9f;
color: white;
box-shadow: none;
}
.pagination .page-item.disabled .page-link {
color: #bbb;
background-color: #f9f9f9;
}
.pagination-info {
color: #999;
font-size: 0.9rem;
}
/* 优化模态框样式 */
.modal-content {
border-radius: 20px;
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: #ffeef2;
border-bottom: 1px solid #ffe0e9;
}
.modal-title {
color: #d23f6e;
font-size: 1.2rem;
font-weight: 600;
}
.modal-body {
padding: 25px;
}
.modal-footer {
padding: 15px 25px;
border-top: 1px solid #ffe0e9;
background-color: #ffeef2;
}
.modal-body p {
color: #666;
font-size: 1rem;
line-height: 1.6;
}
.modal-body p.text-danger {
color: #ff5252 !important;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.modal-body p.text-danger::before {
content: "\f06a";
font-family: "Font Awesome 5 Free";
font-weight: 900;
}
.modal .close {
font-size: 1.5rem;
color: #e67e9f;
opacity: 0.8;
text-shadow: none;
transition: all 0.2s;
}
.modal .close:hover {
opacity: 1;
color: #d23f6e;
}
.modal .btn {
border-radius: 25px;
padding: 8px 20px;
font-weight: 500;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
border: none;
}
.modal .btn-secondary {
background: linear-gradient(135deg, #a0a0a0, #808080);
color: white;
}
.modal .btn-danger {
background: linear-gradient(135deg, #ff7676, #ff5252);
color: white;
}
/* 封面标题栏 */
.cover-title-bar {
position: absolute;
left: 0; right: 0; bottom: 0;
background: linear-gradient(0deg, rgba(233,152,174,0.92) 0%, rgba(255,255,255,0.08) 90%);
color: #fff;
font-size: 1rem;
font-weight: bold;
padding: 10px 14px 7px 14px;
text-shadow: 0 2px 6px rgba(180,0,80,0.14);
line-height: 1.3;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: flex-end;
min-height: 38px;
z-index: 2;
}
.book-card:hover .cover-title-bar {
background: linear-gradient(0deg, #d23f6e 0%, rgba(255,255,255,0.1) 100%);
font-size: 1.07rem;
letter-spacing: .5px;
}
/* 响应式调整 */
@media (max-width: 992px) {
.filter-row {
flex-wrap: wrap;
}
.filter-group {
flex: 1 0 180px;
}
}
@media (max-width: 768px) {
.book-list-container {
padding: 16px;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.action-buttons {
width: 100%;
overflow-x: auto;
padding-bottom: 8px;
flex-wrap: nowrap;
justify-content: flex-start;
}
.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: 1fr;
}
.book-actions {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
.cover-title-bar {
font-size: 0.95rem;
min-height: 27px;
padding: 8px 8px 5px 10px;
}
.book-actions {
grid-template-columns: 1fr;
}
}
================================================================================
File: ./app/static/css/login.css
================================================================================
/* login.css - 登录页面专用样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
:root {
--primary-color: #4a89dc;
--primary-hover: #3b78c4;
--secondary-color: #5cb85c;
--text-color: #333;
--light-text: #666;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #ddd;
--error-color: #e74c3c;
--success-color: #2ecc71;
}
body.dark-mode {
--primary-color: #5a9aed;
--primary-hover: #4a89dc;
--secondary-color: #6bc76b;
--text-color: #f1f1f1;
--light-text: #aaa;
--bg-color: #1a1a1a;
--card-bg: #2c2c2c;
--border-color: #444;
}
body {
background-color: var(--bg-color);
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
min-height: 100vh;
color: var(--text-color);
transition: all 0.3s ease;
}
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
cursor: pointer;
padding: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.overlay {
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
.main-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 20px;
}
.login-container {
background-color: var(--card-bg);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
width: 450px;
padding: 35px;
position: relative;
overflow: hidden;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.logo {
text-align: center;
margin-bottom: 25px;
position: relative;
}
.logo img {
width: 90px;
height: 90px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 5px;
background-color: #fff;
transition: transform 0.3s ease;
}
h1 {
text-align: center;
color: var(--text-color);
margin-bottom: 10px;
font-weight: 600;
font-size: 28px;
}
.subtitle {
text-align: center;
color: var(--light-text);
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-color);
font-weight: 500;
font-size: 14px;
}
.input-with-icon {
position: relative;
}
.input-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--light-text);
}
.form-control {
width: 100%;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px 0 45px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.2);
outline: none;
}
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: var(--light-text);
}
.validation-message {
margin-top: 6px;
font-size: 12px;
color: var(--error-color);
display: none;
}
.validation-message.show {
display: block;
animation: shake 0.5s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.remember-forgot {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.custom-checkbox {
position: relative;
padding-left: 30px;
cursor: pointer;
font-size: 14px;
user-select: none;
color: var(--light-text);
}
.custom-checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 18px;
width: 18px;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 3px;
transition: all 0.2s ease;
}
.custom-checkbox:hover input ~ .checkmark {
border-color: var(--primary-color);
}
.custom-checkbox input:checked ~ .checkmark {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.custom-checkbox input:checked ~ .checkmark:after {
display: block;
}
.custom-checkbox .checkmark:after {
left: 6px;
top: 2px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.forgot-password a {
color: var(--primary-color);
text-decoration: none;
font-size: 14px;
transition: color 0.3s ease;
}
.forgot-password a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.btn-login {
width: 100%;
height: 48px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-login:hover {
background-color: var(--primary-hover);
}
.btn-login:active {
transform: scale(0.98);
}
.btn-login .loading {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.btn-login.loading-state {
color: transparent;
}
.btn-login.loading-state .loading {
display: block;
}
.signup {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: var(--light-text);
}
.signup a {
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.signup a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.features {
display: flex;
justify-content: center;
margin-top: 25px;
gap: 30px;
}
.feature-item {
text-align: center;
font-size: 12px;
color: var(--light-text);
display: flex;
flex-direction: column;
align-items: center;
}
.feature-icon {
margin-bottom: 5px;
font-size: 18px;
}
footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
}
footer a {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
}
.alert {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
color: #721c24;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
@media (max-width: 576px) {
.login-container {
width: 100%;
padding: 25px;
border-radius: 0;
}
.theme-toggle {
top: 10px;
}
.logo img {
width: 70px;
height: 70px;
}
h1 {
font-size: 22px;
}
.main-container {
padding: 0;
}
}
================================================================================
File: ./app/static/css/book-edit.css
================================================================================
/* ========== 优雅粉色主题 - 图书编辑系统 ========== */
: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;
}
================================================================================
File: ./app/static/css/borrow_management.css
================================================================================
/* 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 样式,避免与顶部导航冲突 */
.tabs .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;
}
.search-card .form-control {
border: 1px solid #d9c7b8;
border-radius: 6px;
color: #5d3511;
background-color: #fff9f5;
transition: all 0.3s ease;
font-family: 'Georgia', serif;
}
.search-card .form-control:focus {
border-color: #704214;
box-shadow: 0 0 0 0.2rem rgba(113, 66, 20, 0.15);
background-color: #fff;
}
.search-card .btn-outline-secondary {
color: #704214;
border-color: #d9c7b8;
background-color: transparent;
}
.search-card .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;
}
/* 修改借阅用户显示方式 */
.borrow-table .user-info {
text-align: center;
display: table-cell;
vertical-align: middle;
height: 100%;
}
.borrow-table .user-info a {
color: #5d3511;
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
display: block;
margin-bottom: 8px;
}
.borrow-table .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 - 修复与顶部导航栏冲突 */
.borrow-table .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;
}
.borrow-table .badge-primary {
background-color: #704214;
color: white;
}
.borrow-table .badge-success {
background-color: #5b8a72;
color: white;
}
.borrow-table .badge-danger {
background-color: #a15950;
color: white;
}
.borrow-table .badge-info {
background-color: #6a8da9;
color: white;
}
.borrow-table .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;
}
/* 只为表格中的操作按钮设置样式 */
.actions .btn {
margin-right: 5px;
margin-bottom: 6px;
border-radius: 20px;
padding: 8px 16px;
transition: all 0.3s ease;
font-family: 'Georgia', serif;
letter-spacing: 0.5px;
}
.actions .btn-success {
background-color: #5b8a72;
border-color: #5b8a72;
}
.actions .btn-success:hover,
.actions .btn-success:focus {
background-color: #4a7561;
border-color: #4a7561;
}
.actions .btn-warning {
background-color: #d4a76a;
border-color: #d4a76a;
color: #4a3728;
}
.actions .btn-warning:hover,
.actions .btn-warning:focus {
background-color: #c29355;
border-color: #c29355;
color: #4a3728;
}
.actions .btn-primary {
background-color: #704214;
border-color: #704214;
}
.actions .btn-primary:hover,
.actions .btn-primary:focus {
background-color: #5d3511;
border-color: #5d3511;
box-shadow: 0 0 0 0.2rem rgba(113, 66, 20, 0.25);
}
.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;
}
}
.container .btn-primary {
background-color: #704214;
border-color: #704214;
color: white;
border-radius: 20px;
}
.container .btn-primary:hover,
.container .btn-primary:focus {
background-color: #5d3511;
border-color: #5d3511;
box-shadow: 0 0 0 0.2rem rgba(113, 66, 20, 0.25);
}
================================================================================
File: ./app/static/css/index.css
================================================================================
/* index.css - 仅用于图书管理系统首页/仪表板 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
font-size: 16px;
line-height: 1.6;
}
a {
text-decoration: none;
color: #4a89dc;
}
ul {
list-style: none;
}
/* 应用容器 */
.app-container {
display: flex;
min-height: 100vh;
}
/* 侧边导航栏 */
.sidebar {
width: 250px;
background-color: #2c3e50;
color: #ecf0f1;
padding: 20px 0;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
position: fixed;
height: 100vh;
overflow-y: auto;
}
.logo-container {
padding: 0 20px 20px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
width: 60px;
height: auto;
margin-bottom: 10px;
}
.logo-container h2 {
font-size: 1.2rem;
margin: 10px 0;
color: #ecf0f1;
font-weight: 500;
}
.nav-links li {
margin-bottom: 5px;
}
.nav-links li a {
padding: 10px 20px;
display: flex;
align-items: center;
color: #bdc3c7;
transition: all 0.3s ease;
}
.nav-links li a i {
margin-right: 10px;
font-size: 1.1rem;
width: 20px;
text-align: center;
}
.nav-links li a:hover, .nav-links li.active a {
background-color: #34495e;
color: #ecf0f1;
border-left: 3px solid #4a89dc;
}
.nav-category {
padding: 10px 20px;
font-size: 0.85rem;
text-transform: uppercase;
color: #7f8c8d;
margin-top: 15px;
margin-bottom: 5px;
}
/* 主内容区 */
.main-content {
flex: 1;
margin-left: 250px;
padding: 20px;
}
/* 顶部导航栏 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.search-container {
position: relative;
width: 300px;
}
.search-input {
padding: 10px 15px 10px 40px;
width: 100%;
border: 1px solid #e1e4e8;
border-radius: 20px;
font-size: 14px;
transition: all 0.3s ease;
}
.search-input:focus {
border-color: #4a89dc;
box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.1);
outline: none;
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #8492a6;
}
.user-menu {
display: flex;
align-items: center;
}
.notifications {
margin-right: 20px;
position: relative;
cursor: pointer;
}
.notifications i {
font-size: 1.2rem;
color: #606266;
}
.badge {
position: absolute;
top: -8px;
right: -8px;
background-color: #f56c6c;
color: white;
font-size: 0.7rem;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.user-info {
display: flex;
align-items: center;
position: relative;
cursor: pointer;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #4a89dc;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
font-size: 1.2rem;
}
.user-details {
display: flex;
flex-direction: column;
}
.user-name {
font-weight: 500;
color: #333;
}
.user-role {
font-size: 0.8rem;
color: #8492a6;
}
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
background-color: white;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
padding: 10px 0;
min-width: 150px;
display: none;
z-index: 10;
}
.user-info.active .dropdown-menu {
display: block;
}
.dropdown-menu a {
display: block;
padding: 8px 15px;
color: #606266;
transition: all 0.3s ease;
}
.dropdown-menu a:hover {
background-color: #f5f7fa;
}
.dropdown-menu a i {
margin-right: 8px;
width: 16px;
text-align: center;
}
/* 欢迎区域 */
.welcome-section {
background: linear-gradient(to right, #4a89dc, #5d9cec);
color: white;
padding: 30px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
.welcome-section h1 {
font-size: 1.8rem;
margin-bottom: 5px;
}
.welcome-section p {
font-size: 1rem;
opacity: 0.9;
}
/* 统计卡片样式 */
.stats-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
padding: 20px;
display: flex;
align-items: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.stat-icon {
font-size: 2rem;
color: #4a89dc;
margin-right: 15px;
width: 40px;
text-align: center;
}
.stat-info h3 {
font-size: 0.9rem;
color: #606266;
margin-bottom: 5px;
}
.stat-number {
font-size: 1.8rem;
font-weight: 600;
color: #2c3e50;
}
/* 主要内容区域 */
.main-sections {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.content-section {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
padding: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #edf2f7;
}
.section-header h2 {
font-size: 1.2rem;
color: #2c3e50;
}
.view-all {
font-size: 0.85rem;
color: #4a89dc;
display: flex;
align-items: center;
}
.view-all i {
margin-left: 5px;
transition: transform 0.3s ease;
}
.view-all:hover i {
transform: translateX(3px);
}
/* 图书卡片样式 */
.book-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.book-card {
display: flex;
border: 1px solid #edf2f7;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.book-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
}
.book-cover {
width: 100px;
height: 140px;
min-width: 100px;
background-color: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.book-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.book-info {
padding: 15px;
flex: 1;
display: flex;
flex-direction: column;
}
.book-title {
font-size: 1rem;
margin-bottom: 5px;
color: #2c3e50;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.book-author {
font-size: 0.85rem;
color: #606266;
margin-bottom: 10px;
}
.book-meta {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.book-category {
background-color: #e5f1ff;
color: #4a89dc;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.75rem;
}
.book-status {
font-size: 0.75rem;
font-weight: 500;
}
.book-status.available {
color: #67c23a;
}
.book-status.borrowed {
color: #e6a23c;
}
.borrow-btn {
background-color: #4a89dc;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
margin-top: auto;
transition: background-color 0.3s ease;
}
.borrow-btn:hover {
background-color: #357bc8;
}
/* 通知公告样式 */
.notice-item {
display: flex;
padding: 15px 0;
border-bottom: 1px solid #edf2f7;
}
.notice-item:last-child {
border-bottom: none;
}
.notice-icon {
font-size: 1.5rem;
color: #4a89dc;
margin-right: 15px;
display: flex;
align-items: flex-start;
padding-top: 5px;
}
.notice-content h3 {
font-size: 1rem;
color: #2c3e50;
margin-bottom: 5px;
}
.notice-content p {
font-size: 0.9rem;
color: #606266;
margin-bottom: 10px;
}
.notice-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.notice-time {
font-size: 0.8rem;
color: #8492a6;
}
.renew-btn {
background-color: #ecf5ff;
color: #4a89dc;
border: 1px solid #d9ecff;
padding: 5px 10px;
border-radius: 4px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.3s ease;
}
.renew-btn:hover {
background-color: #4a89dc;
color: white;
border-color: #4a89dc;
}
/* 热门图书区域 */
.popular-section {
margin-top: 20px;
}
.popular-books {
display: flex;
overflow-x: auto;
gap: 15px;
padding-bottom: 10px;
}
.popular-book-item {
display: flex;
background-color: #f8fafc;
border-radius: 8px;
padding: 20px 15px 15px; /* 增加顶部内边距,为角标留出空间 */
min-width: 280px;
position: relative;
margin-top: 10px; /* 在顶部添加一些外边距 */
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.popular-book-item:hover {
transform: translateY(-3px);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.rank-badge {
position: absolute;
top: -8px; /* 略微调高一点 */
left: 10px;
background-color: #4a89dc;
color: white;
width: 28px; /* 增加尺寸 */
height: 28px; /* 增加尺寸 */
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 0.85rem;
font-weight: bold;
z-index: 10; /* 确保它位于其他元素之上 */
box-shadow: 0 2px 4px rgba(0,0,0,0.2); /* 添加阴影使其更突出 */
}
.book-cover.small {
width: 60px;
height: 90px;
min-width: 60px;
margin-right: 15px;
border-radius: 4px;
overflow: hidden; /* 确保图片不会溢出容器 */
}
.book-cover.small img {
width: 100%;
height: 100%;
object-fit: cover; /* 确保图片正确填充容器 */
}
.book-details {
flex: 1;
}
.book-stats {
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 10px;
}
.book-stats span {
font-size: 0.8rem;
color: #8492a6;
}
.book-stats i {
margin-right: 5px;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.stats-container {
grid-template-columns: repeat(2, 1fr);
}
.main-sections {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.sidebar {
width: 70px;
overflow: hidden;
}
.logo-container {
padding: 10px;
}
.logo-container h2 {
display: none;
}
.nav-links li a span {
display: none;
}
.nav-links li a i {
margin-right: 0;
}
.nav-category {
display: none;
}
.main-content {
margin-left: 70px;
}
.search-container {
width: 180px;
}
.book-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 576px) {
.stats-container {
grid-template-columns: 1fr;
}
.top-bar {
flex-direction: column;
gap: 15px;
}
.search-container {
width: 100%;
}
.user-details {
display: none;
}
}
================================================================================
File: ./app/static/css/main.css
================================================================================
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f2f5;
color: #333;
}
.app-container {
display: flex;
min-height: 100vh;
}
/* 侧边栏样式 */
.sidebar {
width: 250px;
background-color: #2c3e50;
color: white;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
position: fixed;
height: 100vh;
overflow-y: auto;
z-index: 1000;
}
.logo-container {
display: flex;
align-items: center;
padding: 20px 15px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
width: 40px;
height: 40px;
margin-right: 10px;
}
.logo-container h2 {
font-size: 1.2rem;
font-weight: 600;
}
.nav-links {
list-style: none;
padding: 15px 0;
}
.nav-category {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
padding: 15px 20px 5px;
color: #adb5bd;
}
.nav-links li {
position: relative;
}
.nav-links li.active {
background-color: rgba(255,255,255,0.1);
}
.nav-links li a {
display: flex;
align-items: center;
padding: 12px 20px;
color: #ecf0f1;
text-decoration: none;
transition: all 0.3s;
}
.nav-links li a:hover {
background-color: rgba(255,255,255,0.05);
}
.nav-links li a i {
margin-right: 10px;
width: 20px;
text-align: center;
}
/* 主内容区样式 */
.main-content {
flex: 1;
margin-left: 250px;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 25px;
background-color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 900;
}
.search-container {
position: relative;
width: 350px;
}
.search-icon {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: #adb5bd;
}
.search-input {
width: 100%;
padding: 10px 10px 10px 35px;
border: 1px solid #dee2e6;
border-radius: 20px;
font-size: 0.9rem;
}
.search-input:focus {
outline: none;
border-color: #4a6cf7;
}
.user-menu {
display: flex;
align-items: center;
}
/* 通知图标样式 */
.notifications {
position: relative;
margin-right: 20px;
}
.notification-icon {
cursor: pointer;
color: #495057;
position: relative;
display: block;
padding: 5px;
}
.badge {
position: absolute;
top: -5px;
right: -5px;
background-color: #e74c3c;
color: white;
font-size: 0.7rem;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
/* 通知下拉菜单 */
.notification-dropdown {
position: absolute;
top: 100%;
right: -10px;
width: 320px;
background-color: white;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
display: none;
z-index: 1000;
max-height: 400px;
overflow-y: auto;
margin-top: 10px;
}
.notification-dropdown.show {
display: block;
}
.notification-dropdown::before {
content: '';
position: absolute;
top: -8px;
right: 15px;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid white;
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #eaeaea;
}
.notification-header h6 {
margin: 0;
font-size: 0.9rem;
font-weight: 600;
}
.mark-all-read {
font-size: 0.8rem;
color: #4a6cf7;
text-decoration: none;
}
.mark-all-read:hover {
text-decoration: underline;
}
.notification-items {
max-height: 300px;
overflow-y: auto;
}
.notification-item {
display: block;
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
color: #333;
text-decoration: none;
transition: background-color 0.2s;
}
.notification-item:hover {
background-color: #f8f9fa;
text-decoration: none;
}
.notification-item.unread {
background-color: #f0f7ff;
}
.notification-title {
font-size: 0.9rem;
margin-bottom: 5px;
font-weight: 600;
}
.notification-text {
font-size: 0.8rem;
color: #666;
margin-bottom: 5px;
}
.notification-time {
font-size: 0.75rem;
color: #999;
display: block;
}
.no-notifications {
padding: 25px;
text-align: center;
color: #999;
}
.dropdown-divider {
height: 1px;
background-color: #eaeaea;
margin: 5px 0;
}
.view-all {
display: block;
text-align: center;
padding: 10px;
color: #4a6cf7;
text-decoration: none;
font-weight: 500;
font-size: 0.9rem;
}
.view-all:hover {
background-color: #f8f9fa;
text-decoration: none;
}
/* 用户信息样式 */
.user-info {
position: relative;
}
.user-info-toggle {
display: flex;
align-items: center;
cursor: pointer;
text-decoration: none;
color: inherit;
}
.user-avatar {
width: 40px;
height: 40px;
background-color: #4a6cf7;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
}
.user-details {
display: flex;
flex-direction: column;
}
.user-name {
font-weight: 600;
font-size: 0.9rem;
}
.user-role {
font-size: 0.8rem;
color: #6c757d;
}
.user-dropdown {
position: absolute;
top: 100%;
right: 0;
background-color: white;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
border-radius: 5px;
width: 200px;
padding: 5px 0;
display: none;
z-index: 1000;
margin-top: 10px;
}
.user-dropdown.show {
display: block;
}
.user-dropdown::before {
content: '';
position: absolute;
top: -8px;
right: 15px;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid white;
}
.user-dropdown a {
display: block;
padding: 10px 15px;
color: #333;
text-decoration: none;
transition: background-color 0.2s;
}
.user-dropdown a:hover {
background-color: #f8f9fa;
}
.user-dropdown a i {
width: 20px;
margin-right: 10px;
text-align: center;
}
.login-link, .register-link {
color: #4a6cf7;
text-decoration: none;
margin-left: 10px;
font-weight: 500;
}
.login-link:hover, .register-link:hover {
text-decoration: underline;
}
/* 内容区域 */
.content-wrapper {
flex: 1;
padding: 20px;
background-color: #f0f2f5;
}
/* 响应式适配 */
@media (max-width: 768px) {
.sidebar {
width: 70px;
overflow: visible;
}
.logo-container h2 {
display: none;
}
.nav-links li a span {
display: none;
}
.main-content {
margin-left: 70px;
}
.user-details {
display: none;
}
.search-container {
width: 200px;
}
.notification-dropdown,
.user-dropdown {
position: fixed;
right: 10px;
width: calc(100% - 80px);
max-width: 320px;
}
}
================================================================================
File: ./app/static/css/log-list.css
================================================================================
/* 全局风格与颜色 */
:root {
--primary-color: #9c88ff;
--secondary-color: #f8a5c2;
--accent-color: #78e08f;
--light-pink: #ffeef8;
--soft-blue: #e5f1ff;
--soft-purple: #f3e5ff;
--soft-pink: #ffeef5;
--soft-red: #ffd8d8;
--text-color: #4a4a4a;
--light-text: #8a8a8a;
--border-radius: 12px;
--box-shadow: 0 6px 15px rgba(0,0,0,0.05);
--transition: all 0.3s ease;
}
/* 整体容器 */
.content-container {
padding: 20px;
font-family: 'Montserrat', sans-serif;
color: var(--text-color);
background-image: linear-gradient(to bottom, var(--soft-blue) 0%, rgba(255,255,255,0.8) 20%, rgba(255,255,255,0.9) 100%);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
position: relative;
overflow: hidden;
}
/* 头部样式 */
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
background: linear-gradient(120deg, var(--soft-purple), var(--soft-pink));
padding: 15px 20px;
border-radius: var(--border-radius);
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
}
.content-header h1 {
margin: 0;
font-size: 24px;
font-weight: 500;
color: #6a3093;
letter-spacing: 0.5px;
}
.content-header .actions {
display: flex;
gap: 12px;
}
/* 闪烁星星效果 */
.sparkle {
position: relative;
display: inline-block;
animation: sparkle 2s infinite;
}
@keyframes sparkle {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.9; }
}
/* 按钮样式 */
.btn {
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
transition: var(--transition);
border: none;
display: flex;
align-items: center;
gap: 6px;
box-shadow: 0 3px 8px rgba(0,0,0,0.05);
}
.btn-blossom {
background: linear-gradient(45deg, #ffcee0, #b5c0ff);
color: #634a7a;
}
.btn-primary-soft {
background: linear-gradient(135deg, #a1c4fd, #c2e9fb);
color: #4a4a4a;
}
.btn-secondary-soft {
background: linear-gradient(135deg, #e2c9fa, #d3f9fb);
color: #4a4a4a;
}
.btn-danger-soft {
background: linear-gradient(135deg, #ffb8c6, #ffdfd3);
color: #a55;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.btn-glow {
animation: glow 1s ease-in-out infinite alternate;
}
@keyframes glow {
from {
box-shadow: 0 0 5px rgba(156, 136, 255, 0.3);
}
to {
box-shadow: 0 0 15px rgba(156, 136, 255, 0.7);
}
}
/* 筛选面板 */
.filter-panel {
background: rgba(255, 255, 255, 0.9);
border-radius: var(--border-radius);
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
border: 1px solid rgba(248, 200, 220, 0.3);
}
.filter-panel-header {
margin-bottom: 15px;
text-align: center;
}
.filter-title {
font-size: 18px;
color: #9c88ff;
font-weight: 500;
font-family: 'Dancing Script', cursive;
font-size: 24px;
}
.snowflake-divider {
display: flex;
justify-content: center;
gap: 15px;
margin: 8px 0;
color: var(--primary-color);
font-size: 14px;
opacity: 0.7;
}
.filter-row {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 15px;
}
.filter-item {
flex: 1;
min-width: 200px;
}
.filter-item label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #7e6d94;
font-size: 14px;
}
.elegant-select,
.elegant-input {
width: 100%;
padding: 10px;
border: 1px solid #e0d0f0;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.8);
color: var(--text-color);
transition: var(--transition);
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}
.elegant-select:focus,
.elegant-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(156, 136, 255, 0.2);
}
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
/* 日期范围样式 */
.date-range-inputs {
padding-top: 15px;
margin-top: 5px;
border-top: 1px dashed #e0d0f0;
}
/* 卡片效果 */
.glass-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
box-shadow: 0 8px 20px rgba(0,0,0,0.05);
border: 1px solid rgba(255, 255, 255, 0.5);
overflow: hidden;
}
.card-body {
padding: 20px;
}
/* 表格样式 */
.table-container {
overflow-x: auto;
border-radius: 8px;
}
.elegant-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
color: var(--text-color);
}
.elegant-table th {
background: linear-gradient(to right, var(--soft-purple), var(--soft-pink));
color: #6a4c93;
font-weight: 500;
text-align: left;
padding: 12px 15px;
font-size: 14px;
border: none;
}
.elegant-table th:first-child {
border-top-left-radius: 8px;
}
.elegant-table th:last-child {
border-top-right-radius: 8px;
}
.elegant-table td {
padding: 12px 15px;
border-bottom: 1px solid rgba(224, 208, 240, 0.3);
font-size: 14px;
transition: var(--transition);
}
.elegant-table tr:last-child td {
border-bottom: none;
}
.elegant-table tr:hover td {
background-color: rgba(248, 239, 255, 0.6);
}
/* 用户徽章样式 */
.user-badge {
background: linear-gradient(45deg, #a1c4fd, #c2e9fb);
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
color: #4a4a4a;
display: inline-block;
}
/* 空数据提示 */
.empty-container {
padding: 30px;
text-align: center;
color: var(--light-text);
}
.empty-container i {
font-size: 40px;
color: #d0c0e0;
margin-bottom: 15px;
}
.empty-container p {
margin: 0;
font-size: 16px;
}
/* 分页样式 */
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 25px;
}
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background: rgba(248, 239, 255, 0.5);
padding: 15px 20px;
border-radius: 25px;
}
.page-btn {
padding: 6px 15px;
border-radius: 20px;
background: linear-gradient(45deg, #e2bbec, #b6cefd);
color: #634a7a;
border: none;
transition: var(--transition);
text-decoration: none;
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
}
.page-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 10px rgba(0,0,0,0.1);
text-decoration: none;
color: #4a3a5a;
}
.page-info {
color: var(--light-text);
font-size: 14px;
}
/* 模态框样式 */
.modal-elegant {
max-width: 400px;
}
.modal-content {
border-radius: 15px;
border: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
background: rgba(255, 255, 255, 0.95);
}
.modal-header {
background: linear-gradient(135deg, #f8c8dc, #c8e7f8);
border-bottom: none;
padding: 15px 20px;
}
.modal-header .modal-title {
color: #634a7a;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.modal-body {
padding: 20px;
}
.modal-message {
color: #7e6d94;
margin-bottom: 15px;
}
.elegant-alert {
background-color: rgba(255, 248, 225, 0.7);
border: 1px solid #ffeeba;
color: #856404;
border-radius: 8px;
padding: 12px 15px;
display: flex;
align-items: center;
gap: 10px;
}
.modal-footer {
background: rgba(248, 239, 255, 0.5);
border-top: none;
padding: 15px 20px;
}
/* 行动画效果 */
.fade-in-row {
animation: fadeIn 0.5s ease-out forwards;
opacity: 0;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式调整 */
@media (max-width: 768px) {
.filter-item {
min-width: 100%;
}
.content-header {
flex-direction: column;
align-items: flex-start;
}
.content-header .actions {
margin-top: 15px;
}
.pagination-container {
flex-direction: column;
gap: 15px;
}
}
================================================================================
File: ./app/static/css/book-form.css
================================================================================
/* ========== 基础重置和变量 ========== */
:root {
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--primary-light: #eff6ff;
--danger-color: #ef4444;
--success-color: #10b981;
--warning-color: #f59e0b;
--info-color: #3b82f6;
--text-dark: #1e293b;
--text-medium: #475569;
--text-light: #64748b;
--text-muted: #94a3b8;
--border-color: #e2e8f0;
--border-focus: #bfdbfe;
--bg-white: #ffffff;
--bg-light: #f8fafc;
--bg-lightest: #f1f5f9;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
--transition-slow: 0.5s ease;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
/* ========== 全局样式 ========== */
.book-form-container {
padding: 24px;
max-width: 1400px;
margin: 0 auto;
font-family: var(--font-sans);
color: var(--text-dark);
}
/* ========== 页头样式 ========== */
.page-header-wrapper {
margin-bottom: 24px;
background-color: var(--bg-white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px;
}
.header-title-section {
display: flex;
flex-direction: column;
}
.page-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-dark);
margin: 0;
}
.subtitle {
margin: 8px 0 0 0;
color: var(--text-medium);
font-size: 0.9rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
.btn-back {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-medium);
background-color: var(--bg-lightest);
border-radius: var(--radius-md);
padding: 8px 16px;
font-size: 0.875rem;
font-weight: 500;
transition: all var(--transition-fast);
text-decoration: none;
box-shadow: var(--shadow-sm);
}
.btn-back:hover {
background-color: var(--border-color);
color: var(--text-dark);
text-decoration: none;
}
.btn-back i {
font-size: 14px;
}
/* 进度条样式 */
.form-progress {
min-width: 180px;
}
.progress-bar-container {
height: 6px;
background-color: var(--bg-lightest);
border-radius: 3px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: var(--primary-color);
border-radius: 3px;
transition: width var(--transition-base);
}
.progress-text {
font-size: 0.75rem;
color: var(--text-light);
text-align: right;
display: block;
margin-top: 4px;
}
/* ========== 表单布局 ========== */
.form-grid {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
}
.form-main-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.form-sidebar {
display: flex;
flex-direction: column;
gap: 24px;
}
/* ========== 表单卡片样式 ========== */
.form-card {
background-color: var(--bg-white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: box-shadow var(--transition-base);
}
.form-card:hover {
box-shadow: var(--shadow-md);
}
.card-header {
padding: 16px 20px;
background-color: var(--bg-white);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
}
.card-title {
font-weight: 600;
color: var(--text-dark);
font-size: 0.9375rem;
}
.card-body {
padding: 20px;
}
.form-section {
padding: 0;
}
/* ========== 表单元素样式 ========== */
.form-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 20px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 500;
color: var(--text-dark);
margin-bottom: 8px;
font-size: 0.9375rem;
}
.form-control {
display: block;
width: 100%;
padding: 10px 14px;
font-size: 0.9375rem;
line-height: 1.5;
color: var(--text-dark);
background-color: var(--bg-white);
background-clip: padding-box;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-control:focus {
border-color: var(--border-focus);
outline: 0;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.form-control::placeholder {
color: var(--text-muted);
}
.form-control:disabled, .form-control[readonly] {
background-color: var(--bg-lightest);
opacity: 0.6;
}
.form-help {
margin-top: 6px;
font-size: 0.8125rem;
color: var(--text-light);
}
.form-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
}
.char-counter {
font-size: 0.8125rem;
color: var(--text-muted);
}
/* 带按钮输入框 */
.input-with-button {
display: flex;
align-items: center;
}
.input-with-button .form-control {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
flex-grow: 1;
}
.btn-append {
height: 42px;
padding: 0 14px;
background-color: var(--bg-lightest);
border: 1px solid var(--border-color);
border-left: none;
border-top-right-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
color: var(--text-medium);
cursor: pointer;
transition: background-color var(--transition-fast);
}
.btn-append:hover {
background-color: var(--border-color);
color: var(--text-dark);
}
/* 文本域 */
textarea.form-control {
min-height: 150px;
resize: vertical;
}
/* 数字输入控件 */
.number-control {
display: flex;
align-items: center;
width: 100%;
border-radius: var(--radius-md);
overflow: hidden;
}
.number-btn {
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--bg-lightest);
border: 1px solid var(--border-color);
color: var(--text-medium);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 1rem;
user-select: none;
}
.number-btn:hover {
background-color: var(--border-color);
color: var(--text-dark);
}
.decrement {
border-top-left-radius: var(--radius-md);
border-bottom-left-radius: var(--radius-md);
}
.increment {
border-top-right-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
.number-control .form-control {
flex: 1;
border-radius: 0;
border-left: none;
border-right: none;
text-align: center;
padding: 10px 0;
}
/* 价格输入 */
.price-input {
position: relative;
}
.currency-symbol {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--text-medium);
}
.price-input .form-control {
padding-left: 30px;
}
.price-slider {
margin-top: 16px;
}
.range-slider {
-webkit-appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background-color: var(--border-color);
outline: none;
margin: 14px 0;
}
.range-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid var(--bg-white);
box-shadow: var(--shadow-sm);
}
.slider-marks {
display: flex;
justify-content: space-between;
font-size: 0.75rem;
color: var(--text-light);
}
/* ========== 按钮样式 ========== */
.btn-primary {
padding: 12px 16px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: var(--radius-md);
font-weight: 500;
font-size: 0.9375rem;
cursor: pointer;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
background-color: var(--primary-hover);
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.btn-primary:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
.btn-primary:active {
transform: translateY(1px);
}
.btn-secondary {
padding: 10px 16px;
background-color: var(--bg-white);
color: var(--text-medium);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-weight: 500;
font-size: 0.9375rem;
cursor: pointer;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all var(--transition-fast);
}
.btn-secondary:hover {
background-color: var(--bg-lightest);
color: var(--text-dark);
}
.btn-secondary:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(226, 232, 240, 0.5);
}
/* ========== 标签输入样式 ========== */
.tag-input-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.tag-input-wrapper .form-control {
flex-grow: 1;
}
.btn-tag-add {
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--primary-color);
border: none;
border-radius: var(--radius-md);
color: white;
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-tag-add:hover {
background-color: var(--primary-hover);
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
min-height: 32px;
}
.tag {
display: inline-flex;
align-items: center;
background-color: var(--primary-light);
border-radius: 50px;
padding: 6px 10px 6px 14px;
font-size: 0.8125rem;
color: var(--primary-color);
transition: all var(--transition-fast);
}
.tag:hover {
background-color: rgba(59, 130, 246, 0.2);
}
.tag-text {
margin-right: 6px;
}
.tag-remove {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
transition: all var(--transition-fast);
}
.tag-remove:hover {
background-color: rgba(59, 130, 246, 0.3);
color: white;
}
/* ========== 封面上传区域 ========== */
.cover-preview-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.cover-preview {
width: 100%;
aspect-ratio: 5/7;
background-color: var(--bg-lightest);
border-radius: var(--radius-md);
overflow: hidden;
cursor: pointer;
transition: all var(--transition-fast);
}
.cover-preview:hover {
background-color: var(--bg-light);
}
.cover-preview.dragover {
background-color: var(--primary-light);
}
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.no-cover-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: var(--text-light);
padding: 24px;
text-align: center;
}
.no-cover-placeholder i {
font-size: 48px;
margin-bottom: 16px;
color: var(--text-muted);
}
.placeholder-tip {
font-size: 0.8125rem;
margin-top: 8px;
color: var(--text-muted);
}
.upload-options {
display: flex;
flex-direction: column;
gap: 12px;
}
.upload-btn-group {
display: flex;
gap: 8px;
}
.btn-upload {
flex-grow: 1;
padding: 10px 16px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: var(--radius-md);
font-weight: 500;
font-size: 0.875rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all var(--transition-fast);
}
.btn-upload:hover {
background-color: var(--primary-hover);
}
.btn-remove {
width: 42px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-medium);
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-remove:hover {
background-color: #fee2e2;
border-color: #fca5a5;
color: #ef4444;
}
.upload-tips {
text-align: center;
font-size: 0.75rem;
color: var(--text-muted);
line-height: 1.5;
}
/* ========== 表单提交区域 ========== */
.form-actions {
display: flex;
flex-direction: column;
gap: 16px;
}
.secondary-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.form-tip {
margin-top: 8px;
font-size: 0.8125rem;
color: var(--text-muted);
text-align: center;
}
.form-tip i {
color: var(--info-color);
margin-right: 4px;
}
/* 必填项标记 */
.required {
color: var(--danger-color);
margin-left: 4px;
}
/* 无效输入状态 */
.is-invalid {
border-color: var(--danger-color) !important;
}
.invalid-feedback {
display: block;
color: var(--danger-color);
font-size: 0.8125rem;
margin-top: 6px;
}
/* ========== Select2 定制 ========== */
.select2-container--classic .select2-selection--single {
height: 42px;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background-color: var(--bg-white);
}
.select2-container--classic .select2-selection--single .select2-selection__rendered {
line-height: 40px;
color: var(--text-dark);
padding-left: 14px;
}
.select2-container--classic .select2-selection--single .select2-selection__arrow {
height: 40px;
border-left: 1px solid var(--border-color);
}
.select2-container--classic .select2-selection--single:focus {
border-color: var(--border-focus);
outline: 0;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
/* ========== 模态框样式 ========== */
.modal-content {
border: none;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
.modal-header {
background-color: var(--bg-white);
border-bottom: 1px solid var(--border-color);
padding: 16px 20px;
}
.modal-title {
font-weight: 600;
color: var(--text-dark);
font-size: 1.125rem;
}
.modal-body {
padding: 20px;
}
.modal-footer {
border-top: 1px solid var(--border-color);
padding: 16px 20px;
}
.modal-btn {
min-width: 100px;
}
/* 裁剪模态框 */
.img-container {
max-height: 500px;
overflow: hidden;
margin-bottom: 20px;
}
#cropperImage {
display: block;
max-width: 100%;
}
.cropper-controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 16px;
}
.control-group {
display: flex;
gap: 8px;
}
.control-btn {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
background-color: var(--bg-lightest);
border: 1px solid var(--border-color);
color: var(--text-medium);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
}
.control-btn:hover {
background-color: var(--border-color);
color: var(--text-dark);
}
/* 图书预览模态框 */
.preview-header {
background-color: var(--bg-white);
border-bottom: 1px solid var(--border-color);
}
.preview-body {
padding: 0;
background-color: var(--bg-lightest);
}
/* 添加到你的CSS文件中 */
.book-preview {
display: flex;
flex-direction: row;
gap: 20px;
}
.preview-cover-section {
flex: 0 0 200px;
}
.preview-details-section {
flex: 1;
}
.book-preview-cover {
height: 280px;
width: 200px;
overflow: hidden;
border-radius: 4px;
border: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
}
.preview-cover-img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-tag {
display: inline-block;
background: #e9ecef;
color: #495057;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
margin-right: 5px;
margin-bottom: 5px;
}
.book-tags-preview {
margin: 15px 0;
}
.book-description-preview {
margin-top: 20px;
}
.section-title {
font-size: 16px;
margin-bottom: 10px;
color: #495057;
border-bottom: 1px solid #dee2e6;
padding-bottom: 5px;
}
.book-meta {
margin-top: 10px;
text-align: center;
}
.book-price {
font-size: 18px;
font-weight: bold;
color: #dc3545;
}
.book-stock {
font-size: 14px;
color: #6c757d;
}
/* 响应式调整 */
@media (max-width: 768px) {
.book-preview {
flex-direction: column;
}
.preview-cover-section {
margin: 0 auto;
}
}
.preview-details-section {
padding: 24px;
}
.book-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-dark);
margin: 0 0 8px 0;
}
.book-author {
color: var(--text-medium);
font-size: 1rem;
margin-bottom: 24px;
}
.book-info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
background-color: var(--bg-white);
border-radius: var(--radius-md);
padding: 16px;
box-shadow: var(--shadow-sm);
margin-bottom: 24px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
font-size: 0.75rem;
color: var(--text-light);
text-transform: uppercase;
}
.info-value {
font-weight: 500;
color: var(--text-dark);
}
.book-tags-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 24px;
}
.preview-tag {
display: inline-block;
background-color: var(--primary-light);
color: var(--primary-color);
padding: 4px 12px;
border-radius: 50px;
font-size: 0.8125rem;
}
.no-tags {
font-size: 0.875rem;
color: var(--text-muted);
}
.book-description-preview {
background-color: var(--bg-white);
border-radius: var(--radius-md);
padding: 16px;
box-shadow: var(--shadow-sm);
}
.section-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-dark);
margin: 0 0 12px 0;
}
.description-content {
font-size: 0.9375rem;
color: var(--text-medium);
line-height: 1.6;
}
.placeholder-text {
color: var(--text-muted);
font-style: italic;
}
.preview-footer {
background-color: var(--bg-white);
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* ========== 通知样式 ========== */
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 12px;
max-width: 320px;
}
.notification {
background-color: var(--bg-white);
border-radius: var(--radius-md);
padding: 12px 16px;
box-shadow: var(--shadow-md);
display: flex;
align-items: center;
gap: 12px;
animation-duration: 0.5s;
}
.success-notification {
border-left: 4px solid var(--success-color);
}
.error-notification {
border-left: 4px solid var(--danger-color);
}
.warning-notification {
border-left: 4px solid var(--warning-color);
}
.info-notification {
border-left: 4px solid var(--info-color);
}
.notification-icon {
color: var(--text-light);
}
.success-notification .notification-icon {
color: var(--success-color);
}
.error-notification .notification-icon {
color: var(--danger-color);
}
.warning-notification .notification-icon {
color: var(--warning-color);
}
.info-notification .notification-icon {
color: var(--info-color);
}
.notification-content {
flex-grow: 1;
}
.notification-content p {
margin: 0;
font-size: 0.875rem;
color: var(--text-dark);
}
.notification-close {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 5px;
transition: color var(--transition-fast);
}
.notification-close:hover {
color: var(--text-medium);
}
/* ========== 动画效果 ========== */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
70% {
box-shadow: 0 0 0 8px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.pulse {
animation: pulse 2s infinite;
}
/* ========== 响应式样式 ========== */
@media (max-width: 1200px) {
.form-grid {
grid-template-columns: 1fr 320px;
gap: 20px;
}
}
@media (max-width: 992px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.form-grid {
grid-template-columns: 1fr;
}
.book-preview {
grid-template-columns: 1fr;
}
.preview-cover-section {
border-right: none;
border-bottom: 1px solid var(--border-color);
padding-bottom: 24px;
}
.book-preview-cover {
max-width: 240px;
margin: 0 auto;
}
}
@media (max-width: 768px) {
.book-form-container {
padding: 16px 12px;
}
.page-header {
padding: 20px;
}
.form-row {
grid-template-columns: 1fr;
gap: 12px;
}
.secondary-actions {
grid-template-columns: 1fr;
}
.card-body {
padding: 16px;
}
.book-info-grid {
grid-template-columns: 1fr;
}
}
.cover-preview {
min-height: 250px;
width: 100%;
border: 1px dashed #ccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.cover-preview img.cover-image {
max-width: 100%;
max-height: 300px;
object-fit: contain;
}
.img-container {
max-height: 500px;
overflow: auto;
}
#cropperImage {
max-width: 100%;
display: block;
}
================================================================================
File: ./app/static/css/borrow_statistics.css
================================================================================
/* app/static/css/borrow_statistics.css */
/* 确保与 statistics.css 兼容的样式 */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 15px;
margin-top: 15px;
}
.stats-item {
background-color: var(--secondary-color);
border-radius: 12px;
padding: 20px 15px;
text-align: center;
transition: all 0.3s ease;
border: 1px solid var(--border-color);
box-shadow: 0 4px 12px var(--shadow-color);
position: relative;
overflow: hidden;
}
.stats-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px var(--shadow-color);
background-color: white;
}
.stats-item::after {
content: '';
position: absolute;
bottom: -15px;
right: -15px;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: var(--primary-color);
opacity: 0.1;
transition: all 0.3s ease;
}
.stats-item:hover::after {
transform: scale(1.2);
opacity: 0.2;
}
.stats-value {
font-size: 26px;
font-weight: 700;
margin-bottom: 8px;
color: var(--accent-color);
display: flex;
justify-content: center;
align-items: center;
height: 40px;
position: relative;
}
.stats-value::before {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 2px;
background-color: var(--primary-color);
border-radius: 2px;
}
.stats-title {
font-size: 14px;
color: var(--light-text);
font-weight: 500;
}
.loading {
text-align: center;
padding: 40px;
color: var(--light-text);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loader {
border: 4px solid rgba(244, 188, 204, 0.3);
border-top: 4px solid var(--accent-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 修复图表容器 */
.chart-container {
margin-bottom: 30px;
}
.chart-wrapper {
position: relative;
height: 300px;
width: 100%;
}
.trend-chart .chart-wrapper {
height: 330px;
}
/* 确保图表正确渲染 */
canvas {
max-width: 100%;
height: auto !important;
}
/* 添加一些女性化的装饰元素 */
.chart-container::before {
content: '';
position: absolute;
top: -15px;
left: -15px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
opacity: 0.4;
z-index: 0;
}
.chart-container::after {
content: '';
position: absolute;
bottom: -15px;
right: -15px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
opacity: 0.4;
z-index: 0;
}
/* 新增部分 */
.intro-text {
text-align: center;
margin-bottom: 25px;
font-size: 16px;
font-weight: 300;
color: var(--light-text);
font-style: italic;
}
.insights-container {
background-color: var(--secondary-color);
border-radius: 15px;
padding: 25px;
margin-top: 30px;
box-shadow: 0 5px 20px var(--shadow-color);
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
}
.insights-container h3 {
color: var(--accent-color);
font-size: 1.3rem;
margin-bottom: 15px;
font-weight: 600;
text-align: center;
position: relative;
}
.insights-container h3::after {
content: '';
position: absolute;
bottom: -5px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 2px;
background: linear-gradient(to right, var(--secondary-color), var(--accent-color), var(--secondary-color));
border-radius: 3px;
}
.insights-content {
line-height: 1.6;
color: var(--text-color);
text-align: center;
position: relative;
z-index: 1;
}
.insights-container::before {
content: '';
position: absolute;
top: -30px;
right: -30px;
width: 100px;
height: 100px;
border-radius: 50%;
background-color: var(--primary-color);
opacity: 0.1;
}
/* 优雅的动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeInUp {
animation: fadeInUp 0.8s ease forwards;
}
/* 确保响应式布局 */
@media (max-width: 768px) {
.chart-row {
flex-direction: column;
}
.half {
width: 100%;
min-width: 0;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.chart-wrapper {
height: 250px;
}
}
================================================================================
File: ./app/static/css/categories.css
================================================================================
/* 分类管理页面样式 */
.categories-container {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.card {
margin-bottom: 20px;
border: 1px solid rgba(0,0,0,0.125);
border-radius: 0.25rem;
}
.card-header {
padding: 0.75rem 1.25rem;
background-color: rgba(0,0,0,0.03);
border-bottom: 1px solid rgba(0,0,0,0.125);
font-weight: 600;
}
.card-body {
padding: 1.25rem;
}
.category-table {
border: 1px solid #eee;
}
.category-table th {
background-color: #f8f9fa;
}
.no-categories {
text-align: center;
padding: 30px;
color: #888;
}
.no-categories i {
font-size: 48px;
color: #ddd;
margin-bottom: 10px;
}
/* 通知弹窗 */
.notification-alert {
position: fixed;
top: 20px;
right: 20px;
min-width: 300px;
z-index: 1050;
}
/* 响应式调整 */
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
================================================================================
File: ./app/static/css/user-edit.css
================================================================================
/* 用户编辑页面样式 */
.user-edit-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 页面标题和操作按钮 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.page-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}
.page-header .actions {
display: flex;
gap: 10px;
}
/* 卡片样式 */
.card {
border: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.card-body {
padding: 25px;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-group label {
font-weight: 500;
margin-bottom: 8px;
color: #333;
display: block;
}
.form-control {
height: auto;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control:focus {
border-color: #4c84ff;
box-shadow: 0 0 0 0.2rem rgba(76, 132, 255, 0.25);
}
.form-control[readonly] {
background-color: #f8f9fa;
opacity: 0.7;
}
.form-text {
font-size: 0.85rem;
margin-top: 5px;
}
.form-row {
margin-right: -15px;
margin-left: -15px;
display: flex;
flex-wrap: wrap;
}
.col-md-6 {
flex: 0 0 50%;
max-width: 50%;
padding-right: 15px;
padding-left: 15px;
}
.col-md-12 {
flex: 0 0 100%;
max-width: 100%;
padding-right: 15px;
padding-left: 15px;
}
/* 用户信息框 */
.user-info-box {
margin-top: 20px;
margin-bottom: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
}
.info-item {
flex: 0 0 auto;
margin-right: 30px;
margin-bottom: 10px;
}
.info-label {
font-weight: 500;
color: #666;
margin-right: 5px;
}
.info-value {
color: #333;
}
/* 表单操作区域 */
.form-actions {
display: flex;
justify-content: flex-start;
gap: 10px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.btn {
padding: 8px 16px;
border-radius: 4px;
transition: all 0.2s ease;
}
.btn-primary {
background-color: #4c84ff;
border-color: #4c84ff;
}
.btn-primary:hover {
background-color: #3a70e9;
border-color: #3a70e9;
}
.btn-secondary {
background-color: #f8f9fa;
border-color: #ddd;
color: #333;
}
.btn-secondary:hover {
background-color: #e9ecef;
border-color: #ccc;
}
.btn-outline-secondary {
color: #6c757d;
border-color: #6c757d;
}
.btn-outline-secondary:hover {
color: #fff;
background-color: #6c757d;
border-color: #6c757d;
}
/* 表单分隔线 */
.form-divider {
height: 1px;
background-color: #f0f0f0;
margin: 30px 0;
}
/* 警告和错误状态 */
.is-invalid {
border-color: #dc3545 !important;
}
.invalid-feedback {
display: block;
width: 100%;
margin-top: 5px;
font-size: 0.85rem;
color: #dc3545;
}
/* 成功消息样式 */
.alert-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
/* 错误消息样式 */
.alert-error, .alert-danger {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
}
.col-md-6, .col-md-12 {
flex: 0 0 100%;
max-width: 100%;
}
.page-header {
flex-direction: column;
align-items: flex-start;
}
.page-header .actions {
margin-top: 15px;
}
.user-info-box {
flex-direction: column;
}
.info-item {
margin-right: 0;
}
}
================================================================================
File: ./app/static/css/user-profile.css
================================================================================
/* 用户个人中心页面样式 */
.profile-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 页面标题 */
.page-header {
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.page-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}
/* 个人中心内容布局 */
.profile-content {
display: flex;
gap: 30px;
}
/* 左侧边栏 */
.profile-sidebar {
flex: 0 0 300px;
background-color: #f8f9fa;
border-radius: 8px;
padding: 25px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 右侧主要内容 */
.profile-main {
flex: 1;
min-width: 0; /* 防止内容溢出 */
}
/* 用户头像容器 */
.user-avatar-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 25px;
padding-bottom: 25px;
border-bottom: 1px solid #e9ecef;
}
/* 大头像样式 */
.user-avatar.large {
width: 120px;
height: 120px;
border-radius: 50%;
background-color: #4c84ff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
margin-bottom: 15px;
box-shadow: 0 4px 8px rgba(76, 132, 255, 0.2);
}
.user-name {
font-size: 1.5rem;
margin: 10px 0 5px;
color: #333;
}
.user-role {
font-size: 0.9rem;
color: #6c757d;
margin: 0;
}
/* 用户统计信息 */
.user-stats {
display: flex;
justify-content: space-between;
margin-bottom: 25px;
padding-bottom: 25px;
border-bottom: 1px solid #e9ecef;
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-value {
font-size: 1.8rem;
font-weight: 600;
color: #4c84ff;
line-height: 1;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.85rem;
color: #6c757d;
}
/* 账户信息样式 */
.account-info {
margin-bottom: 10px;
}
.account-info .info-row {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
font-size: 0.95rem;
}
.account-info .info-label {
color: #6c757d;
font-weight: 500;
}
.account-info .info-value {
color: #333;
text-align: right;
word-break: break-all;
}
/* 选项卡导航样式 */
.nav-tabs {
border-bottom: 1px solid #dee2e6;
margin-bottom: 25px;
}
.nav-tabs .nav-link {
border: none;
color: #6c757d;
padding: 12px 15px;
margin-right: 5px;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
}
.nav-tabs .nav-link:hover {
color: #4c84ff;
border-bottom-color: #4c84ff;
}
.nav-tabs .nav-link.active {
font-weight: 500;
color: #4c84ff;
border-bottom: 2px solid #4c84ff;
background-color: transparent;
}
.nav-tabs .nav-link i {
margin-right: 5px;
}
/* 表单区域 */
.form-section {
padding: 20px;
background-color: #f9f9fb;
border-radius: 8px;
margin-bottom: 20px;
}
.form-section h4 {
margin-top: 0;
margin-bottom: 20px;
color: #333;
font-size: 1.2rem;
font-weight: 500;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
font-weight: 500;
margin-bottom: 8px;
color: #333;
display: block;
}
.form-control {
height: auto;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control:focus {
border-color: #4c84ff;
box-shadow: 0 0 0 0.2rem rgba(76, 132, 255, 0.25);
}
.form-text {
font-size: 0.85rem;
margin-top: 5px;
}
/* 表单操作区域 */
.form-actions {
margin-top: 25px;
display: flex;
justify-content: flex-start;
}
.btn {
padding: 10px 20px;
border-radius: 4px;
transition: all 0.2s ease;
}
.btn-primary {
background-color: #4c84ff;
border-color: #4c84ff;
}
.btn-primary:hover {
background-color: #3a70e9;
border-color: #3a70e9;
}
/* 活动记录选项卡 */
.activity-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.activity-filter {
display: flex;
align-items: center;
gap: 10px;
}
.activity-filter label {
margin-bottom: 0;
}
.activity-filter select {
width: auto;
}
/* 活动时间线 */
.activity-timeline {
padding: 20px;
background-color: #f9f9fb;
border-radius: 8px;
min-height: 300px;
position: relative;
}
.timeline-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 250px;
}
.timeline-loading p {
margin-top: 15px;
color: #6c757d;
}
.timeline-item {
position: relative;
padding-left: 30px;
padding-bottom: 25px;
border-left: 2px solid #dee2e6;
}
.timeline-item:last-child {
border-left: none;
}
.timeline-icon {
position: absolute;
left: -10px;
top: 0;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #4c84ff;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 10px;
}
.timeline-content {
background-color: white;
border-radius: 6px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
padding: 15px;
}
.timeline-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.timeline-title {
font-weight: 500;
color: #333;
margin: 0;
}
.timeline-time {
font-size: 0.85rem;
color: #6c757d;
}
.timeline-details {
color: #555;
font-size: 0.95rem;
}
.timeline-type-login .timeline-icon {
background-color: #4caf50;
}
.timeline-type-borrow .timeline-icon {
background-color: #2196f3;
}
.timeline-type-return .timeline-icon {
background-color: #ff9800;
}
/* 通知样式 */
.alert {
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.alert-success {
color: #155724;
background-color: #d4edda;
border: 1px solid #c3e6cb;
}
.alert-error, .alert-danger {
color: #721c24;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
/* 响应式调整 */
@media (max-width: 992px) {
.profile-content {
flex-direction: column;
}
.profile-sidebar {
flex: none;
width: 100%;
margin-bottom: 20px;
}
.user-stats {
justify-content: space-around;
}
}
================================================================================
File: ./app/static/css/browse.css
================================================================================
/* 图书浏览页面样式 */
/* 全局容器 */
.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%;
}
}
================================================================================
File: ./app/static/css/inventory-adjust.css
================================================================================
/* 迪士尼主题库存管理页面样式 */
/* 基础样式 */
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;
}
================================================================================
File: ./app/static/css/overdue.css
================================================================================
/* 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;
}
}
================================================================================
File: ./app/static/css/announcement-manage.css
================================================================================
.announcement-manage-container {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
border-bottom: 1px solid #e3e3e3;
padding-bottom: 15px;
}
.filter-container {
margin-bottom: 25px;
}
.filter-form {
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.filter-form .form-group {
margin-bottom: 0;
min-width: 200px;
}
.announcement-table {
background-color: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
border-radius: 8px;
}
.announcement-table th {
background-color: #f8f9fa;
white-space: nowrap;
}
.announcement-title {
font-weight: 500;
color: #333;
text-decoration: none;
display: block;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.announcement-title:hover {
color: #007bff;
text-decoration: underline;
}
.btn-group {
display: flex;
gap: 5px;
}
.pagination-container {
margin-top: 30px;
display: flex;
justify-content: center;
}
.no-records {
text-align: center;
padding: 50px 20px;
background-color: #f8f9fa;
border-radius: 8px;
color: #6c757d;
}
.no-records i {
font-size: 3rem;
margin-bottom: 15px;
}
.no-records p {
font-size: 1.2rem;
}
================================================================================
File: ./app/static/css/user_activity.css
================================================================================
/* app/static/css/user_activity.css */
.data-table .rank {
font-weight: 700;
text-align: center;
}
.data-table .borrow-count {
font-weight: 600;
color: #007bff;
}
================================================================================
File: ./app/static/css/user-form.css
================================================================================
/* 用户表单样式 - 甜美风格 */
.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);
}
}
================================================================================
File: ./app/static/css/book-import.css
================================================================================
/* 图书批量导入页面样式 - 女性风格优化版 */
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500&family=Playfair+Display:wght@400;700&display=swap');
:root {
--primary-color: #e083b8;
--primary-light: #f8d7e9;
--secondary-color: #89c2d9;
--accent-color: #a76eb8;
--text-color: #555;
--light-text: #888;
--dark-text: #333;
--border-radius: 12px;
--box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
}
body {
background-color: #fff6f9;
font-family: 'Montserrat', sans-serif;
color: var(--text-color);
}
.import-container {
padding: 30px;
position: relative;
overflow: hidden;
}
/* 页眉样式 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #f0d3e6;
}
.fancy-title {
font-family: 'Playfair Display', serif;
font-size: 2.5rem;
color: var(--accent-color);
text-shadow: 1px 1px 2px rgba(167, 110, 184, 0.2);
letter-spacing: 1px;
margin: 0;
position: relative;
}
.fancy-title::after {
content: "";
position: absolute;
bottom: -10px;
left: 0;
width: 60px;
height: 3px;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
border-radius: 3px;
}
.subtitle {
font-size: 1.5rem;
font-weight: 300;
color: var(--light-text);
margin-left: 10px;
}
.btn-return {
padding: 8px 20px;
background-color: transparent;
color: var(--accent-color);
border: 2px solid var(--primary-light);
border-radius: 25px;
transition: all 0.3s ease;
font-weight: 500;
box-shadow: 0 3px 8px rgba(167, 110, 184, 0.1);
}
.btn-return:hover {
background-color: var(--primary-light);
color: var(--accent-color);
transform: translateY(-3px);
box-shadow: 0 5px 12px rgba(167, 110, 184, 0.2);
}
/* 卡片样式 */
.card {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
background-color: #ffffff;
margin-bottom: 30px;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12);
}
.card-header {
background: linear-gradient(135deg, #f9f1f7, #fcf6fa);
padding: 20px 25px;
border-bottom: 1px solid #f0e1ea;
}
.card-header h4 {
font-family: 'Playfair Display', serif;
color: var(--accent-color);
margin: 0;
font-size: 1.5rem;
}
.sparkle {
color: var(--primary-color);
margin-right: 8px;
animation: sparkle 2s infinite;
}
@keyframes sparkle {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.card-body {
padding: 30px;
}
/* 表单样式 */
.elegant-label {
font-weight: 500;
color: var(--dark-text);
margin-bottom: 12px;
font-size: 1.1rem;
display: block;
}
.custom-file {
position: relative;
display: inline-block;
width: 100%;
margin-bottom: 15px;
}
.custom-file-input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
z-index: 2;
}
.custom-file-label {
padding: 15px 20px;
background-color: #f9f2f7;
color: var(--light-text);
border: 2px dashed #e9d6e5;
border-radius: var(--border-radius);
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
}
.custom-file-label:hover {
background-color: #f4e8f0;
border-color: var(--primary-color);
}
.has-file .custom-file-label {
background-color: #e6f3ff;
border-color: var(--secondary-color);
color: var(--secondary-color);
font-weight: 500;
}
.import-btn {
background: linear-gradient(45deg, var(--primary-color), var(--accent-color));
border: none;
padding: 15px 30px;
color: white;
font-size: 1.1rem;
font-weight: 500;
border-radius: 30px;
margin-top: 15px;
transition: all 0.3s ease;
box-shadow: 0 8px 15px rgba(167, 110, 184, 0.3);
}
.import-btn:hover {
transform: translateY(-3px);
box-shadow: 0 12px 20px rgba(167, 110, 184, 0.4);
background: linear-gradient(45deg, var(--accent-color), var(--primary-color));
}
/* 分隔线 */
.divider {
display: flex;
align-items: center;
margin: 30px 0;
color: var(--light-text);
}
.divider:before,
.divider:after {
content: "";
flex: 1;
border-bottom: 1px solid #f0d3e6;
}
.divider-content {
padding: 0 10px;
color: var(--primary-color);
font-size: 1.2rem;
}
/* 导入说明样式 */
.import-instructions {
margin-top: 10px;
padding: 25px;
background: linear-gradient(to bottom right, #fff, #fafafa);
border-radius: var(--border-radius);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.03);
}
.instruction-title {
font-family: 'Playfair Display', serif;
color: var(--accent-color);
margin-bottom: 20px;
font-size: 1.4rem;
border-bottom: 2px solid var(--primary-light);
padding-bottom: 10px;
display: inline-block;
}
.instruction-content {
color: var(--text-color);
line-height: 1.6;
}
.elegant-list {
list-style-type: none;
padding-left: 5px;
margin-top: 15px;
}
.elegant-list li {
margin-bottom: 12px;
position: relative;
padding-left: 25px;
line-height: 1.5;
}
.elegant-list li:before {
content: "\f054";
font-family: "Font Awesome 5 Free";
font-weight: 900;
color: var(--primary-color);
position: absolute;
left: 0;
top: 2px;
font-size: 12px;
}
.field-name {
font-family: 'Courier New', monospace;
background-color: #f6f6f6;
padding: 2px 8px;
border-radius: 4px;
color: #9c5bb5;
font-weight: 600;
font-size: 0.9rem;
}
.required-field {
color: var(--dark-text);
}
.required-badge {
background-color: #fce1e9;
color: #e25a86;
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 12px;
margin-left: 5px;
vertical-align: middle;
font-weight: 600;
}
/* 模板下载样式 */
.template-download {
margin-top: 30px;
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #f0f9ff, #f5f0ff);
border-radius: var(--border-radius);
border: 1px solid #e0f0ff;
}
.template-download p {
color: var(--dark-text);
margin-bottom: 15px;
font-weight: 500;
}
.download-btn {
background-color: white;
color: var(--accent-color);
border: 2px solid var(--primary-light);
padding: 10px 25px;
border-radius: 25px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
}
.download-btn:hover {
background-color: var(--accent-color);
color: white;
border-color: var(--accent-color);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(167, 110, 184, 0.2);
}
/* 悬浮元素 - 冰雪奇缘和天空之城风格 */
.floating-elements {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: -1;
}
.snowflake {
position: absolute;
opacity: 0.7;
border-radius: 50%;
background: radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, rgba(230,240,255,0.3) 70%, transparent 100%);
animation: float 20s linear infinite;
}
.snowflake-1 {
width: 20px;
height: 20px;
top: 10%;
left: 10%;
}
.snowflake-2 {
width: 15px;
height: 15px;
top: 20%;
right: 20%;
}
.snowflake-3 {
width: 25px;
height: 25px;
bottom: 30%;
left: 30%;
}
.snowflake-4 {
width: 18px;
height: 18px;
bottom: 15%;
right: 15%;
}
.flower {
position: absolute;
width: 30px;
height: 30px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath fill='%23e083b8' d='M50 15c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm-25 25c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm50 0c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm-25 25c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10z'/%3E%3Ccircle fill='%23f8d7e9' cx='50' cy='50' r='10'/%3E%3C/svg%3E");
background-size: contain;
background-repeat: no-repeat;
opacity: 0.5;
animation: rotate 25s linear infinite, float 20s ease-in-out infinite;
}
.flower-1 {
top: 70%;
left: 5%;
}
.flower-2 {
top: 15%;
right: 5%;
}
@keyframes float {
0% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(30px) translateX(15px);
}
50% {
transform: translateY(50px) translateX(-15px);
}
75% {
transform: translateY(20px) translateX(25px);
}
100% {
transform: translateY(0) translateX(0);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 响应式调整 */
@media (max-width: 992px) {
.import-container {
padding: 20px 15px;
}
.fancy-title {
font-size: 2rem;
}
.subtitle {
font-size: 1.2rem;
}
}
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.card-body {
padding: 20px 15px;
}
.import-instructions {
padding: 15px;
}
.fancy-title {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
display: block;
margin-left: 0;
margin-top: 5px;
}
}
/* 添加到book-import.css文件末尾 */
/* 导入消息样式 */
.import-message {
margin-top: 15px;
}
.import-message .alert {
border-radius: var(--border-radius);
padding: 15px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
border: none;
}
.import-message .alert-success {
background-color: #e6f7ee;
color: #28a745;
}
.import-message .alert-warning {
background-color: #fff8e6;
color: #ffc107;
}
.import-message .alert-danger {
background-color: #feecf0;
color: #dc3545;
}
.import-message .alert-info {
background-color: #e6f3f8;
color: #17a2b8;
}
.import-message .alert i {
margin-right: 8px;
}
/* 导入过程中的飘落元素 */
.falling-element {
position: absolute;
z-index: 1000;
pointer-events: none;
opacity: 0.8;
}
.falling-flower {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath fill='%23e083b8' d='M50 15c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm-25 25c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm50 0c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10zm-25 25c-5 0-10 5-10 10s5 10 10 10 10-5 10-10-5-10-10-10z'/%3E%3Ccircle fill='%23f8d7e9' cx='50' cy='50' r='10'/%3E%3C/svg%3E");
background-size: contain;
background-repeat: no-repeat;
animation: fallAndSpin 5s linear forwards;
}
.falling-snowflake {
background: radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, rgba(230,240,255,0.3) 70%, transparent 100%);
border-radius: 50%;
animation: fall 5s linear forwards;
}
@keyframes fall {
0% {
transform: translateY(-50px) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
100% {
transform: translateY(calc(100vh - 100px)) rotate(359deg);
opacity: 0;
}
}
@keyframes fallAndSpin {
0% {
transform: translateY(-50px) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
100% {
transform: translateY(calc(100vh - 100px)) rotate(720deg);
opacity: 0;
}
}
/* 导入过程中按钮样式 */
.import-btn:disabled {
background: linear-gradient(45deg, #f089b7, #b989d9);
opacity: 0.7;
cursor: not-allowed;
}
.import-btn:disabled .fa-spinner {
margin-right: 10px;
}
/* 文件上传成功状态样式 */
.has-file .custom-file-label {
background-color: #e6f7ee;
border-color: #28a745;
color: #28a745;
}
/* 添加文件类型图标 */
.has-file .custom-file-label::before {
content: "\f56f"; /* Excel文件图标 */
font-family: "Font Awesome 5 Free";
font-weight: 900;
margin-right: 8px;
}
================================================================================
File: ./app/static/css/statistics.css
================================================================================
/* app/static/css/statistics.css */
:root {
/* Soft & Elegant Palette */
--color-primary-light-pink: #FCE4EC; /* 淡粉色 */
--color-primary-milk-white: #FFF8F0; /* 奶白色 */
--color-primary-apricot: #FFDAB9; /* 浅杏色 */
--color-aux-rose-gold: #B76E79; /* 玫瑰金 */
--color-aux-light-purple: #E6E6FA; /* 淡紫色 */
--color-aux-soft-gray: #D3D3D3; /* 柔和的灰色 */
--color-accent-berry-red: #8C2D5A; /* 深一点的浆果红 */
--font-serif-elegant: 'Playfair Display', serif;
--font-serif-lora: 'Lora', serif;
--font-sans-clean: 'Open Sans', sans-serif;
--font-script-delicate: 'Sacramento', cursive;
--font-serif-garamond: 'EB Garamond', serif;
/* Derived/General Usage */
--background-main: var(--color-primary-milk-white);
--background-container: #FFFFFF;
--text-main: #5D5053; /* A darker, softer, slightly desaturated rose-brown */
--text-soft: #8A797C;
--text-heading: var(--color-aux-rose-gold);
--text-accent: var(--color-accent-berry-red);
--border-soft: var(--color-aux-soft-gray);
--border-decorative: var(--color-primary-light-pink);
--shadow-soft: rgba(183, 110, 121, 0.1); /* Soft shadow based on rose gold */
--shadow-subtle: rgba(0, 0, 0, 0.05);
/* Fallback for old variables - some might still be used by unchanged CSS */
--primary-color: var(--color-primary-light-pink);
--secondary-color: var(--color-primary-apricot); /* Or #FFF8F0 for a lighter secondary */
--accent-color: var(--color-aux-rose-gold);
--text-color: var(--text-main);
--light-text: var(--text-soft);
--border-color: var(--border-soft);
--shadow-color: var(--shadow-soft);
--hover-color: #F8E0E6; /* Lighter pink for hover */
}
body {
background-color: var(--background-main);
color: var(--text-main);
font-family: var(--font-sans-clean);
font-weight: 300; /* Lighter default font weight */
line-height: 1.7; /* Increased line height */
}
.statistics-container {
padding: 40px 30px; /* Increased padding */
max-width: 1100px; /* Slightly adjusted max-width */
margin: 40px auto; /* More margin for breathing room */
background-color: var(--background-container);
border-radius: 16px; /* Softer, larger border-radius */
box-shadow: 0 8px 25px var(--shadow-soft); /* Softer shadow */
position: relative;
overflow: hidden;
}
.page-title {
color: var(--text-heading);
margin-bottom: 35px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border-decorative); /* Thinner, delicate line */
text-align: center;
font-family: var(--font-serif-elegant);
font-size: 2.8em; /* Larger, more prominent */
font-weight: 700;
letter-spacing: 0.5px;
}
/* Simplified page title decoration */
.page-title:after {
content: '';
display: block;
width: 80px; /* Shorter line */
height: 2px; /* Thinner line */
margin: 12px auto 0;
background: var(--color-aux-rose-gold); /* Solid accent color */
border-radius: 2px;
/* animation: wave 3s infinite linear; Removed wave animation for elegance */
}
/* @keyframes wave {
0%, 100% { background-position-x: 0%; }
50% { background-position-x: 100%; }
} */
/* Quote Banner - Styled for elegance */
.quote-banner {
background-color: var(--color-primary-light-pink); /* Soft pink background */
border-radius: 12px; /* Softer radius */
padding: 25px 35px; /* Ample padding */
margin: 0 auto 40px; /* Increased bottom margin */
max-width: 75%;
text-align: center;
box-shadow: 0 4px 15px rgba(183, 110, 121, 0.08); /* Very subtle shadow */
border-left: 3px solid var(--color-aux-rose-gold);
border-right: 3px solid var(--color-aux-rose-gold);
position: relative;
}
.quote-banner p {
font-family: var(--font-serif-garamond), serif; /* Elegant serif for quote */
font-style: italic;
color: var(--color-accent-berry-red); /* Berry red for emphasis */
font-size: 1.1em; /* Slightly larger */
margin: 0;
letter-spacing: 0.2px;
line-height: 1.6;
}
.quote-banner:before,
.quote-banner:after {
content: """; /* Using """ for opening */
font-family: var(--font-serif-elegant), serif; /* Consistent elegant font */
font-size: 50px; /* Adjusted size */
color: var(--color-aux-rose-gold); /* Rose gold for quotes */
opacity: 0.4; /* Softer opacity */
position: absolute;
top: 0px;
}
.quote-banner:before {
left: 15px;
}
.quote-banner:after {
content: """; /* Using """ for closing */
right: 15px;
top: auto; /* Adjust position for closing quote mark */
bottom: -20px;
}
/* Stats Grid - main navigation cards container */
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 30px; /* Increased gap for more whitespace */
margin: 40px auto; /* Adjusted margin */
max-width: 900px; /* Adjusted max-width */
}
.stats-grid .stats-card {
position: relative;
background-color: var(--background-container);
border-radius: 12px; /* Softer radius */
overflow: hidden;
box-shadow: 0 6px 18px var(--shadow-subtle); /* More subtle shadow */
transition: transform 0.35s cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
text-decoration: none;
color: var(--text-main);
border: 1px solid #F0E8E9; /* Very light, almost invisible border */
min-height: 260px; /* Ensure cards have enough height */
padding: 0;
}
.card-inner { /* This class is directly inside stats-card links */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 25px; /* Ample padding */
height: 100%;
position: relative;
z-index: 2;
background: transparent; /* Make it transparent to show card background */
transition: background-color 0.3s ease;
}
.stats-grid .stats-card:hover {
transform: translateY(-6px); /* Slightly less aggressive transform */
box-shadow: 0 10px 25px var(--shadow-soft); /* Enhanced shadow on hover */
border-color: var(--color-primary-light-pink);
}
.stats-grid .stats-card:hover .card-inner {
/* background: rgba(255, 248, 240, 0.5); */ /* Optional: very subtle hover background on inner part */
}
.stats-grid .card-icon {
font-size: 36px; /* Slightly smaller icon */
margin-bottom: 18px;
color: var(--color-aux-rose-gold);
background-color: var(--color-primary-milk-white); /* Milk white for icon background */
width: 70px; /* Adjusted size */
height: 70px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
box-shadow: 0 3px 8px rgba(183, 110, 121, 0.15); /* Subtle shadow for icon */
transition: transform 0.3s ease, color 0.3s ease;
}
.stats-grid .stats-card:hover .card-icon {
transform: scale(1.08) rotate(3deg);
color: var(--color-accent-berry-red); /* Icon color change on hover */
}
.stats-grid .card-title {
font-family: var(--font-serif-lora);
font-size: 1.45em; /* Adjusted size */
font-weight: 600;
margin-bottom: 12px;
color: var(--text-heading);
position: relative;
display: inline-block;
}
.stats-grid .card-title:after { /* Decorative line under card title */
content: '';
position: absolute;
bottom: -6px; /* Positioned slightly below */
left: 50%;
transform: translateX(-50%) scaleX(0); /* Start scaled to 0 */
width: 60%; /* Line width relative to title */
height: 1.5px;
background-color: var(--color-primary-light-pink); /* Light pink line */
transition: transform 0.35s ease-out;
transform-origin: center;
}
.stats-grid .stats-card:hover .card-title:after {
transform: translateX(-50%) scaleX(1); /* Scale to full on hover */
}
.stats-grid .card-description {
font-family: var(--font-sans-clean);
font-size: 0.9em;
color: var(--text-soft);
line-height: 1.5;
max-width: 90%; /* Prevent text from touching edges */
}
/* Card Decoration - Subtle background elements */
.card-decoration {
position: absolute;
bottom: -40px; /* Adjusted position */
right: -40px;
width: 120px; /* Smaller decoration */
height: 120px;
border-radius: 50%;
background-color: var(--color-primary-light-pink); /* Light pink base */
opacity: 0.15; /* More subtle opacity */
transition: all 0.5s ease;
z-index: 1;
}
.stats-card:hover .card-decoration { /* Use stats-card hover for decoration */
transform: scale(1.4);
opacity: 0.25;
}
/* Specific card decorations with more subtle emoji styling */
.card-decoration:before { /* General style for emoji if used */
position: absolute;
font-size: 24px; /* Smaller emoji */
top: 50%; /* Centered better */
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.3; /* Very subtle */
color: var(--color-aux-rose-gold); /* Themed color */
}
.book-decoration:before { content: '📚'; }
.trend-decoration:before { content: '📈'; }
.user-decoration:before { content: '👥'; }
.overdue-decoration:before { content: '⏰'; }
/* Page Decoration - Floating elements */
.page-decoration {
position: absolute;
width: 180px; /* Slightly smaller */
height: 180px;
border-radius: 50%;
background: linear-gradient(45deg, var(--color-primary-apricot), var(--color-aux-light-purple), var(--color-primary-light-pink)); /* New gradient */
opacity: 0.15; /* More subtle */
z-index: -1; /* Ensure it's behind content */
}
.page-decoration.left {
top: -80px; /* Adjusted position */
left: -80px;
animation: floatLeft 18s ease-in-out infinite;
}
.page-decoration.right {
bottom: -80px;
right: -80px;
animation: floatRight 20s ease-in-out infinite;
}
@keyframes floatLeft {
0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); }
25% { transform: translate(15px, 20px) rotate(8deg) scale(1.05); }
50% { transform: translate(5px, 35px) rotate(15deg) scale(1); }
75% { transform: translate(25px, 10px) rotate(5deg) scale(1.05); }
}
@keyframes floatRight {
0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); }
25% { transform: translate(-15px, -18px) rotate(-7deg) scale(1.05); }
50% { transform: translate(-10px, -30px) rotate(-12deg) scale(1); }
75% { transform: translate(-22px, -12px) rotate(-6deg) scale(1.05); }
}
/* --- Unchanged CSS from this point onwards as per request for elements not in index.html --- */
/* --- (Or elements whose styling should largely be preserved unless overridden by above general styles) --- */
.breadcrumb {
margin-bottom: 20px;
font-size: 14px;
color: var(--light-text); /* Will use new --light-text */
}
.breadcrumb a {
color: var(--accent-color); /* Will use new --accent-color */
text-decoration: none;
transition: all 0.3s ease;
}
.breadcrumb a:hover {
text-decoration: underline;
color: var(--color-accent-berry-red); /* More specific hover */
}
.breadcrumb .current-page {
color: var(--text-color); /* Will use new --text-color */
font-weight: 500;
}
/* 原始卡片菜单 - Unchanged as it's not used in the provided HTML */
.stats-menu {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-top: 30px;
}
/* 原始卡片样式 - This .stats-card is different from .stats-grid .stats-card. Keeping for other pages. */
/* However, some properties might be inherited if not specific enough. */
/* Adding a more specific selector to avoid conflict if this old style is needed elsewhere */
.stats-menu > .stats-card {
background-color: var(--secondary-color);
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 12px var(--shadow-color);
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.4s;
text-decoration: none;
color: var(--text-color);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
border: 1px solid var(--border-color);
}
.stats-menu > .stats-card:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 8px 20px var(--shadow-color);
border-color: var(--primary-color);
}
/* Card icon/title/description for .stats-menu > .stats-card */
.stats-menu > .stats-card .card-icon {
font-size: 40px;
margin-bottom: 15px;
color: var(--accent-color);
/* Resetting some properties from .stats-grid .card-icon if they conflict */
background-color: transparent;
width: auto;
height: auto;
box-shadow: none;
}
.stats-menu > .stats-card .card-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
font-family: var(--font-sans-clean); /* Keep it simple for this version */
color: var(--text-color); /* Default text color for these */
}
.stats-menu > .stats-card .card-title:after {
display: none; /* No line for this version */
}
.stats-menu > .stats-card .card-description {
font-size: 14px;
color: var(--light-text);
font-family: var(--font-sans-clean);
}
.filter-section {
margin-bottom: 25px;
display: flex;
align-items: center;
background-color: var(--color-primary-milk-white); /* Updated bg */
padding: 12px 18px;
border-radius: 10px;
border: 1px dashed var(--border-decorative); /* Updated border */
}
.filter-label {
font-weight: 500;
margin-right: 10px;
color: var(--text-main); /* Updated text */
}
.filter-select {
padding: 8px 15px;
border: 1px solid var(--border-soft); /* Updated border */
border-radius: 8px; /* Softer radius */
background-color: white;
color: var(--text-main);
font-size: 0.95em;
font-family: var(--font-sans-clean);
transition: border-color 0.3s, box-shadow 0.3s;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23B76E79' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); /* Updated arrow color */
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 30px;
}
.filter-select:focus {
outline: none;
border-color: var(--color-aux-rose-gold); /* Updated focus color */
box-shadow: 0 0 0 3px rgba(183, 110, 121, 0.2); /* Updated focus shadow */
}
.ml-20 {
margin-left: 20px;
}
.chart-container {
background-color: white;
border-radius: 12px; /* Softer radius */
padding: 25px;
box-shadow: 0 4px 15px var(--shadow-soft); /* Updated shadow */
margin-bottom: 35px;
position: relative;
height: 400px;
border: 1px solid var(--border-decorative); /* Updated border */
overflow: hidden;
}
.chart-container canvas {
max-height: 100%;
z-index: 1;
position: relative;
}
.chart-decoration { /* These are for charts, distinct from page/card decorations */
position: absolute;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(45deg, var(--color-primary-light-pink), var(--color-primary-apricot)); /* Updated gradient */
opacity: 0.4; /* Softer opacity */
z-index: 0;
}
.chart-decoration.left {
top: -15px;
left: -15px;
}
.chart-decoration.right {
bottom: -15px;
right: -15px;
}
.floating {
animation: floating 6s ease-in-out infinite;
}
@keyframes floating {
0% { transform: translate(0, 0) scale(1); }
50% { transform: translate(8px, 8px) scale(1.05); } /* Softer float */
100% { transform: translate(0, 0) scale(1); }
}
.chart-container.half {
height: auto;
min-height: 400px;
padding-bottom: 40px;
}
.chart-container.half .chart-wrapper {
height: 340px;
padding-bottom: 20px;
}
canvas#category-chart {
max-height: 100%;
margin-bottom: 20px;
padding-bottom: 20px;
position: relative;
}
.chart-container.half::before,
.chart-container.half::after {
width: 40px;
height: 40px;
opacity: 0.2; /* Softer opacity */
}
.chart-container.half .chart-wrapper {
position: relative;
}
.chart-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 30px;
}
.half {
flex: 1 1 calc(50% - 10px);
min-width: 300px;
}
/* 表格容器样式 */
.table-container {
margin-bottom: 30px;
position: relative;
overflow: hidden;
border-radius: 12px;
box-shadow: 0 4px 20px var(--shadow-subtle);
}
.data-table {
width: 100%;
border-collapse: collapse; /* 修改为collapse以解决边框问题 */
border-spacing: 0;
border-radius: 10px; /* 保持圆角 */
overflow: hidden;
box-shadow: 0 2px 10px var(--shadow-subtle); /* 保持阴影 */
font-family: var(--font-sans-clean); /* 确保一致字体 */
}
.data-table th, .data-table td {
padding: 14px 18px;
text-align: left;
border-bottom: 1px solid var(--border-decorative); /* 保持底部边框 */
vertical-align: middle; /* 确保所有内容垂直居中 */
box-sizing: border-box; /* 确保边框计算在单元格尺寸内 */
}
.data-table td {
font-size: 0.95em;
}
.data-table th {
background-color: var(--color-primary-light-pink); /* Lighter pink for header */
font-weight: 600; /* Was 600, can be 400 for softer look */
color: var(--text-heading); /* Rose gold text for header */
letter-spacing: 0.5px;
font-size: 1em;
border-bottom: 2px solid var(--color-aux-rose-gold);
}
.data-table tr {
transition: background-color 0.3s;
}
.data-table tr:nth-child(even) {
background-color: var(--color-primary-milk-white); /* Milk white for even rows */
}
.data-table tr:nth-child(odd) {
background-color: white;
}
.data-table tr:last-child td {
border-bottom: none;
}
.data-table tr:hover {
background-color: #FEF6F8; /* Very light pink on hover */
}
/* 表格特定列的样式 */
.data-table th:first-child,
.data-table td:first-child {
text-align: center; /* 排名居中 */
position: relative; /* 确保相对定位 */
}
.data-table th:nth-child(2),
.data-table td:nth-child(2) {
text-align: center; /* 封面图片居中 */
}
.data-table th:last-child,
.data-table td:last-child {
text-align: center; /* 借阅次数居中显示 */
}
.loading-row td {
text-align: center;
padding: 30px;
color: var(--text-soft); /* Updated text color */
}
.loading-animation {
display: flex;
align-items: center;
justify-content: center;
}
.loading-animation:before {
content: '📖';
margin-right: 10px;
animation: bookFlip 2s infinite;
display: inline-block;
color: var(--color-aux-rose-gold); /* Themed color */
}
@keyframes bookFlip {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(180deg); }
100% { transform: rotateY(360deg); }
}
.dot-animation {
display: inline-block;
animation: dotAnimation 1.5s infinite;
}
@keyframes dotAnimation {
0% { opacity: 0.3; }
50% { opacity: 1; }
100% { opacity: 0.3; }
}
.stats-cards { /* This is for the small summary cards, different from .stats-grid */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
/* Style for .stats-cards > .stats-card if they exist */
.stats-cards > .stats-card {
background-color: var(--background-container);
border: 1px solid var(--border-decorative);
padding: 20px;
border-radius: 10px;
box-shadow: 0 3px 10px var(--shadow-subtle);
text-align: center;
}
.stats-cards .stats-card .card-value { /* Assuming .card-value is inside these cards */
font-size: 2em; /* Adjusted size */
font-weight: 700;
margin-bottom: 8px;
color: var(--color-accent-berry-red); /* Berry red for value */
font-family: var(--font-serif-elegant);
}
.stats-cards .stats-card .card-title { /* Title within these small cards */
font-family: var(--font-sans-clean);
font-size: 0.95em;
color: var(--text-soft);
font-weight: 400;
}
.stats-cards .stats-card .card-icon { /* Icon within these small cards */
font-size: 1.8em;
color: var(--color-aux-rose-gold);
margin-bottom: 10px;
}
/* Quote Container - Appears distinct from .quote-banner, kept for other pages */
.quote-container {
text-align: center;
margin: 40px auto 20px;
max-width: 600px;
font-style: italic;
color: var(--text-main); /* Updated text */
padding: 20px;
background-color: var(--color-primary-apricot); /* Apricot background */
border-radius: 12px; /* Softer radius */
position: relative;
font-family: var(--font-serif-garamond);
box-shadow: 0 3px 10px var(--shadow-subtle);
}
.quote-container:before,
.quote-container:after {
content: """;
font-size: 50px;
font-family: var(--font-serif-elegant);
position: absolute;
color: var(--color-aux-rose-gold); /* Rose gold quotes */
opacity: 0.3; /* Softer */
}
.quote-container:before {
top: -5px;
left: 10px;
}
.quote-container:after {
content: """;
bottom: -25px;
right: 10px;
}
.quote-container p {
position: relative;
z-index: 1;
margin-bottom: 10px;
font-size: 1.05em; /* Adjusted */
line-height: 1.6;
}
.quote-author {
display: block;
font-size: 0.9em;
font-style: normal;
text-align: right;
color: var(--text-soft); /* Updated text */
font-family: var(--font-sans-clean);
}
/* Book list title - for table pages */
.book-list-title {
text-align: center;
margin-bottom: 25px;
color: var(--text-heading); /* Rose gold */
font-family: var(--font-serif-lora); /* Lora for this title */
font-size: 1.8em; /* Adjusted */
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
padding: 0 20px;
}
.book-icon { /* General book icon if used with this title */
font-size: 0.9em;
margin: 0 8px;
opacity: 0.85;
color: var(--color-aux-rose-gold);
}
.column-icon {
font-size: 0.9em;
margin-right: 5px;
opacity: 0.8;
color: var(--color-aux-rose-gold);
}
.book-list-title:before,
.book-list-title:after {
content: '';
position: absolute;
height: 1.5px; /* Thinner line */
background: linear-gradient(to right, transparent, var(--color-primary-light-pink), transparent); /* Softer gradient */
width: 70px;
top: 50%;
}
.book-list-title:before {
right: 100%;
margin-right: 15px;
}
.book-list-title:after {
left: 100%;
margin-left: 15px;
}
/* 表格中的图标样式 */
.data-table .borrow-count {
font-weight: 600;
color: var(--text-heading);
position: relative;
display: block; /* 修改为block以占据整个单元格 */
text-align: center; /* 确保文本居中 */
font-size: 1em;
}
.data-table .borrow-count:after {
content: '📚';
font-size: 12px;
margin-left: 5px;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: translateY(5px);
display: inline-block;
color: var(--color-aux-rose-gold);
}
.data-table tr:hover .borrow-count:after {
opacity: 0.7; /* Softer opacity */
transform: translateY(0);
}
/* 排名列样式 */
.data-table .rank {
font-weight: 700;
text-align: center;
position: relative;
font-size: 1.1em;
color: var(--text-heading);
font-family: var(--font-serif-lora);
padding: 5px 15px; /* 基本内边距 */
}
/* 前三名奖牌样式 */
.data-table tr:nth-child(1) .rank:before,
.data-table tr:nth-child(2) .rank:before,
.data-table tr:nth-child(3) .rank:before {
position: absolute;
font-size: 1.2em;
left: 5px; /* 左侧位置 */
top: 50%;
transform: translateY(-50%);
opacity: 0.85;
}
/* 分别设置每个奖牌的内容 */
.data-table tr:nth-child(1) .rank:before {
content: '🏆';
}
.data-table tr:nth-child(2) .rank:before {
content: '🥈';
}
.data-table tr:nth-child(3) .rank:before {
content: '🥉';
}
/* 确保所有排名单元格的对齐一致 */
.data-table td:first-child {
text-align: center;
}
.book-title { /* In data tables */
position: relative;
text-decoration: none;
display: inline-block;
font-weight: 600; /* Bolder for emphasis */
color: var(--text-accent); /* Berry red for book titles */
transition: color 0.3s;
}
.data-table tr:hover .book-title {
color: var(--color-aux-rose-gold); /* Rose gold on hover */
}
.book-title:after { /* Underline effect for book titles in tables */
content: '';
position: absolute;
width: 100%;
height: 1.5px;
bottom: -3px;
left: 0;
background-color: var(--color-primary-light-pink); /* Light pink underline */
transform: scaleX(0);
transform-origin: bottom right;
transition: transform 0.3s ease-out;
}
tr:hover .book-title:after {
transform: scaleX(1);
transform-origin: bottom left;
}
/* Data table image styling */
.data-table img {
width: 50px; /* Slightly smaller */
height: 75px;
object-fit: cover;
border-radius: 6px; /* Softer radius */
box-shadow: 0 2px 6px rgba(0,0,0,0.08); /* Softer shadow */
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 2px solid white;
}
.data-table tr:hover img {
transform: scale(1.1); /* Slightly more pop */
box-shadow: 0 4px 10px rgba(0,0,0,0.12);
border-color: var(--color-primary-light-pink); /* Pink border on hover */
}
.data-table .author {
font-style: italic;
color: var(--text-soft); /* Softer text for author */
font-size: 0.9em;
}
.no-data {
text-align: center;
padding: 40px;
color: var(--text-soft);
background-color: var(--color-primary-milk-white); /* Milk white background */
border-radius: 12px;
font-style: italic;
border: 1px dashed var(--border-decorative); /* Decorative dashed border */
font-family: var(--font-serif-garamond);
}
/* 书籍行动画 */
#ranking-table-body tr {
transition: transform 0.3s ease, opacity 0.3s ease, background-color 0.3s ease; /* Added background-color */
}
#ranking-table-body tr:hover {
transform: translateX(3px); /* Subtle shift */
}
/* Animation shared */
.fade-in { /* This is a custom class, not from animate.css */
animation: customFadeIn 0.6s ease forwards; /* Renamed to avoid conflict */
opacity: 0;
transform: translateY(15px); /* Slightly more travel */
}
@keyframes customFadeIn { /* Renamed */
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive adjustments */
@media (max-width: 992px) { /* Adjusted breakpoint */
.stats-grid {
max-width: 95%;
gap: 20px; /* Smaller gap on medium screens */
}
.stats-grid .stats-card {
min-height: 240px;
}
.page-title {
font-size: 2.4em;
}
.quote-banner {
max-width: 85%;
}
}
@media (max-width: 768px) {
.statistics-container {
padding: 30px 20px;
margin: 20px auto;
}
.page-title {
font-size: 2em;
}
.quote-banner {
max-width: 90%;
padding: 20px;
}
.quote-banner:before,
.quote-banner:after {
font-size: 35px;
}
.quote-banner:after {
bottom: -15px;
}
.stats-grid {
grid-template-columns: 1fr; /* Single column for cards */
gap: 25px;
max-width: 450px; /* Max width for single column */
}
.stats-grid .stats-card {
min-height: auto; /* Auto height for single column */
height: auto; /* Ensure this is not fixed */
padding-bottom: 20px; /* Ensure padding for content */
}
.stats-grid .card-inner {
padding: 20px;
}
.chart-row {
flex-direction: column;
}
.half {
width: 100%;
flex-basis: 100%; /* Ensure it takes full width */
}
.stats-cards { /* Small summary cards */
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.filter-section {
flex-wrap: wrap;
padding: 10px 15px;
}
.filter-select {
width: 100%;
}
.ml-20 {
margin-left: 0;
margin-top: 10px;
}
.page-decoration { /* Make page decorations smaller or hide on mobile */
width: 120px;
height: 120px;
opacity: 0.1;
}
.page-decoration.left {
top: -60px;
left: -60px;
}
.page-decoration.right {
bottom: -60px;
right: -60px;
}
.data-table th, .data-table td {
padding: 10px 12px;
font-size: 0.9em;
}
.data-table img {
width: 40px;
height: 60px;
}
}
@media (max-width: 480px) {
.page-title {
font-size: 1.8em;
}
.quote-banner p {
font-size: 1em;
}
.stats-grid .card-title {
font-size: 1.3em;
}
.stats-grid .card-description {
font-size: 0.85em;
}
.stats-grid .card-icon {
width: 60px;
height: 60px;
font-size: 30px;
}
.statistics-container {
margin: 15px auto;
padding: 20px 15px;
}
.page-decoration {
display: none; /* Hide complex decorations on very small screens */
}
/* 移动端表格调整 */
.data-table .rank:before {
left: -5px; /* 小屏幕上减少偏移量 */
font-size: 1.2em;
}
.data-table .rank {
padding: 5px 8px; /* 减少内边距 */
}
}
================================================================================
File: ./app/static/css/inventory-list.css
================================================================================
/* 全局变量设置 */
: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,') 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;
}
}
================================================================================
File: ./app/static/css/my_borrows.css
================================================================================
/* 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;
}
}
================================================================================
File: ./app/static/css/announcement-list.css
================================================================================
/* Fresh & Vibrant Style for Announcement List */
:root {
--mint-green: #A8E6CF;
--pale-yellow: #FFD3B6;
--coral-pink: #FFAAA5;
--sky-blue: #BDE4F4;
--clean-white: #FFFFFF;
--bright-orange: #FF8C69; /* Emphasis for buttons/key info */
--lemon-yellow: #FFFACD;
--text-dark: #424242;
--text-medium: #616161; /* Slightly darker medium for better contrast */
--text-light: #888888; /* Adjusted light text */
--font-title: 'Poppins', sans-serif;
--font-body: 'Nunito Sans', sans-serif;
--card-shadow: 0 5px 18px rgba(0, 0, 0, 0.07);
--card-hover-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
--border-radius-main: 16px; /* Slightly larger radius for a softer look */
--border-radius-small: 10px;
}
/* Apply base font and background to body (likely in base.css) */
body {
font-family: var(--font-body);
background-color: #fcfdfe; /* Very light off-white, almost white */
color: var(--text-dark);
line-height: 1.65;
}
.announcement-container {
padding: 25px 30px;
max-width: 960px;
margin: 25px auto;
background-color: var(--clean-white);
border-radius: var(--border-radius-main);
/* Optional: Subtle gradient background for the container itself */
/* background-image: linear-gradient(to bottom right, #f0f9ff, #ffffff); */
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eef2f5; /* Softer border */
}
.page-header h1 {
font-family: var(--font-title);
font-size: 2.2rem;
font-weight: 700;
color: var(--text-dark);
margin: 0;
display: flex;
align-items: center;
}
.page-icon { /* Icon for page title */
color: var(--coral-pink);
margin-right: 12px;
font-size: 1.8rem;
}
/* Optional: Style for a "Create New" button if you add one */
.btn-fresh-create {
background-color: var(--mint-green);
color: #3a7c68; /* Darker mint for text */
border: none;
padding: 10px 20px;
border-radius: 25px;
font-family: var(--font-body);
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
font-size: 0.9rem;
box-shadow: 0 2px 8px rgba(168, 230, 207, 0.4);
}
.btn-fresh-create:hover {
background-color: #97e0c6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(168, 230, 207, 0.5);
}
.btn-fresh-create i {
margin-right: 8px;
}
.announcement-list {
margin-top: 20px;
display: grid;
gap: 25px; /* Spacing between announcement items */
}
.announcement-item {
background-color: var(--clean-white);
border-radius: var(--border-radius-main);
box-shadow: var(--card-shadow);
padding: 25px 30px;
position: relative; /* For pin-badge */
transition: transform 0.25s ease-out, box-shadow 0.25s ease-out;
overflow: hidden; /* If using pseudo-elements for borders */
}
.announcement-item:hover {
transform: translateY(-5px) scale(1.01);
box-shadow: var(--card-hover-shadow);
}
.announcement-item.pinned {
/* Use a top border or a more distinct background */
border-top: 4px solid var(--mint-green);
background-color: #f6fffb; /* Light mint */
}
.pin-badge {
position: absolute;
top: 0px;
right: 0px;
background: linear-gradient(135deg, var(--mint-green), #8fdcc3);
color: var(--clean-white);
padding: 6px 15px 6px 20px;
border-radius: 0 0 0 var(--border-radius-main); /* Creative corner */
font-size: 0.8rem;
font-weight: 600;
font-family: var(--font-body);
box-shadow: -2px 2px 8px rgba(168, 230, 207, 0.3);
}
.pin-badge i {
margin-right: 6px;
font-size: 0.75rem;
}
.announcement-header {
display: flex;
justify-content: space-between;
align-items: flex-start; /* Align date to top if title wraps */
margin-bottom: 10px;
}
.announcement-header h3 {
margin: 0;
font-size: 1.4rem; /* Slightly larger title */
font-family: var(--font-title);
font-weight: 600;
line-height: 1.3;
margin-right: 15px; /* Space between title and date */
}
.announcement-header h3 a {
color: var(--text-dark);
text-decoration: none;
transition: color 0.2s ease;
}
.announcement-header h3 a:hover {
color: var(--coral-pink);
}
.date {
color: var(--text-light);
font-size: 0.85rem;
font-weight: 400;
white-space: nowrap; /* Prevent date from wrapping */
padding-top: 3px; /* Align better with h3 */
}
.announcement-preview {
margin: 15px 0;
color: var(--text-medium);
line-height: 1.7;
font-size: 0.95rem;
letter-spacing: 0.1px;
}
.announcement-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #f0f4f7; /* Lighter separator */
}
.publisher {
color: var(--text-light);
font-size: 0.85rem;
display: flex;
align-items: center;
}
.publisher i {
margin-right: 6px;
color: var(--sky-blue);
}
.read-more {
color: var(--bright-orange);
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
font-family: var(--font-body);
display: inline-flex; /* Allows icon alignment and hover effects */
align-items: center;
padding: 6px 12px;
border-radius: 20px;
background-color: transparent;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.read-more:hover {
background-color: var(--bright-orange);
color: var(--clean-white);
transform: translateX(3px);
}
.read-more i {
margin-left: 6px;
transition: transform 0.2s ease-in-out;
}
/* .read-more:hover i {
transform: translateX(4px);
} */ /* Handled by transform on .read-more now */
/* Pagination Styles (copied and adapted from previous for consistency) */
.pagination-container {
margin-top: 40px;
display: flex;
justify-content: center;
}
.pagination {
display: flex;
list-style: none;
padding-left: 0;
}
.pagination .page-item .page-link {
color: var(--coral-pink);
background-color: var(--clean-white);
border: 1px solid var(--pale-yellow);
margin: 0 5px; /* Slightly more spacing */
border-radius: 50%;
width: 40px; /* Slightly larger */
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.95rem;
font-family: var(--font-body);
transition: all 0.25s ease-in-out;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.pagination .page-item .page-link:hover {
background-color: var(--pale-yellow);
color: var(--coral-pink);
border-color: var(--coral-pink);
text-decoration: none;
transform: translateY(-2px);
box-shadow: 0 3px 8px rgba(255, 211, 182, 0.5);
}
.pagination .page-item.active .page-link {
background-color: var(--coral-pink);
border-color: var(--coral-pink);
color: var(--clean-white);
box-shadow: 0 4px 10px rgba(255, 170, 165, 0.6);
}
.pagination .page-item.disabled .page-link {
color: #cccccc;
background-color: #f9f9f9;
border-color: #eeeeee;
pointer-events: none;
box-shadow: none;
}
.no-records {
text-align: center;
padding: 60px 30px;
background-color: #fffaf8; /* Very light coral/yellow tint */
border-radius: var(--border-radius-main);
color: var(--text-medium);
margin-top: 20px;
box-shadow: var(--card-shadow);
}
.no-records-icon {
width: 60px;
height: 60px;
margin-bottom: 20px;
opacity: 0.9;
}
/* Fallback for FontAwesome if SVG doesn't load or is removed */
.no-records .fas.fa-info-circle {
font-size: 3.5rem;
margin-bottom: 20px;
color: var(--coral-pink);
opacity: 0.8;
}
.no-records p {
font-size: 1.15rem;
font-family: var(--font-body);
font-weight: 600;
color: var(--text-dark);
line-height: 1.6;
}
================================================================================
File: ./app/static/css/notifications.css
================================================================================
/* Fresh & Vibrant Style for Notifications */
:root {
--mint-green: #A8E6CF;
--pale-yellow: #FFD3B6;
--coral-pink: #FFAAA5;
--sky-blue: #BDE4F4;
--clean-white: #FFFFFF;
--bright-orange: #FF8C69; /* Emphasis for buttons/key info */
--lemon-yellow: #FFFACD; /* Can be used for subtle highlights */
--text-dark: #424242; /* Slightly softer than pure black */
--text-medium: #757575;
--text-light: #9E9E9E;
--font-title: 'Poppins', sans-serif;
--font-body: 'Nunito Sans', sans-serif;
--card-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
--card-hover-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
--border-radius-main: 12px;
--border-radius-small: 8px;
}
/* Apply base font and background to body (likely in base.css, but good for context) */
body {
font-family: var(--font-body);
background-color: var(--clean-white); /* Or a very light tint like #FDFCFA */
color: var(--text-dark);
line-height: 1.6;
font-weight: 400;
}
.notifications-container {
padding: 25px 30px;
max-width: 900px;
margin: 20px auto;
background-color: var(--clean-white);
/* Optional: add a subtle pattern or a large soft circular gradient */
/* background-image: linear-gradient(135deg, var(--mint-green) -20%, var(--clean-white) 30%); */
border-radius: var(--border-radius-main);
/* box-shadow: 0 10px 30px rgba(168, 230, 207, 0.2); */ /* Subtle shadow for container */
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #f0f0f0; /* Softer border */
}
.page-header h1 {
font-family: var(--font-title);
font-size: 2rem; /* Slightly larger */
font-weight: 600;
color: var(--text-dark);
margin: 0;
}
/* Fresh Action Button Style */
.btn-fresh-action {
background-color: var(--bright-orange);
color: var(--clean-white);
border: none;
padding: 10px 20px;
border-radius: 25px; /* Pill shape */
font-family: var(--font-body);
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
font-size: 0.9rem;
box-shadow: 0 2px 8px rgba(255, 140, 105, 0.3);
}
.btn-fresh-action:hover {
background-color: #ff7b5a; /* Slightly darker orange */
color: var(--clean-white);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 140, 105, 0.4);
}
.btn-fresh-action i {
margin-right: 8px;
}
.filter-tabs {
display: flex;
margin-bottom: 25px;
gap: 10px;
/* border-bottom: 2px solid var(--pale-yellow); */ /* Optional subtle line */
}
.filter-tab {
padding: 10px 20px;
color: var(--text-medium);
text-decoration: none;
border-radius: var(--border-radius-small); /* Rounded tabs */
font-weight: 600;
font-size: 0.95rem;
transition: all 0.3s ease;
border-bottom: 3px solid transparent; /* Underline effect for active */
}
.filter-tab:hover {
color: var(--coral-pink);
background-color: rgba(255, 170, 165, 0.1); /* Light coral tint on hover */
}
.filter-tab.active {
color: var(--coral-pink);
border-bottom-color: var(--coral-pink);
/* background-color: var(--coral-pink); */
/* color: var(--clean-white); */
}
.notifications-list {
margin-top: 20px;
display: grid;
gap: 20px;
}
.notification-card {
background-color: var(--clean-white);
border-radius: var(--border-radius-main);
box-shadow: var(--card-shadow);
padding: 20px 25px;
transition: transform 0.25s ease, box-shadow 0.25s ease;
display: flex; /* For icon alignment */
align-items: flex-start; /* Align icon to top of content */
gap: 15px;
border-left: 5px solid transparent; /* Placeholder for unread state */
}
.notification-icon-area {
font-size: 1.5rem;
color: var(--sky-blue);
padding-top: 5px; /* Align with title */
}
.notification-card.unread .notification-icon-area {
color: var(--mint-green);
}
.notification-card:hover {
transform: translateY(-4px);
box-shadow: var(--card-hover-shadow);
}
.notification-card.unread {
border-left-color: var(--mint-green);
background-color: #f6fffb; /* Very light mint */
}
.notification-content {
flex-grow: 1;
}
.notification-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0;
margin-bottom: 8px;
font-size: 1.15rem; /* Adjusted size */
font-family: var(--font-title);
font-weight: 600;
}
.notification-title a {
color: var(--text-dark);
text-decoration: none;
transition: color 0.2s ease;
}
.notification-title a:hover {
color: var(--coral-pink);
}
.unread-badge {
background-color: var(--bright-orange);
color: white;
font-size: 0.7rem;
padding: 4px 10px;
border-radius: 15px; /* Pill shape */
margin-left: 10px;
font-weight: 600;
letter-spacing: 0.5px;
}
.notification-text {
color: var(--text-medium);
margin-bottom: 15px;
line-height: 1.6;
font-size: 0.9rem;
letter-spacing: 0.2px;
}
.notification-meta {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--text-light);
font-size: 0.8rem;
}
.notification-type {
background-color: var(--sky-blue); /* Sky Blue for type */
color: #3E84A8; /* Darker text for contrast on sky blue */
padding: 3px 10px;
border-radius: var(--border-radius-small);
font-weight: 600;
}
.notification-time {
font-style: italic;
}
/* Pagination */
.pagination-container {
margin-top: 30px;
display: flex;
justify-content: center;
}
.pagination {
display: flex;
list-style: none;
padding-left: 0;
}
.pagination .page-item .page-link {
color: var(--coral-pink);
background-color: var(--clean-white);
border: 1px solid var(--pale-yellow);
margin: 0 4px;
border-radius: 50%; /* Circular pagination items */
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.2s ease-in-out;
}
.pagination .page-item .page-link:hover {
background-color: var(--pale-yellow);
color: var(--coral-pink);
border-color: var(--coral-pink);
text-decoration: none;
}
.pagination .page-item.active .page-link {
background-color: var(--coral-pink);
border-color: var(--coral-pink);
color: var(--clean-white);
box-shadow: 0 2px 5px rgba(255, 170, 165, 0.5);
}
.pagination .page-item.disabled .page-link {
color: #ccc;
background-color: #f8f8f8;
border-color: #eee;
pointer-events: none;
}
.no-records {
text-align: center;
padding: 50px 20px;
background-color: #fafffd; /* Very light mint/yellow mix */
border-radius: var(--border-radius-main);
color: var(--text-medium);
margin-top: 20px;
}
.no-records-icon { /* Style for the inline SVG */
width: 60px;
height: 60px;
margin-bottom: 20px;
opacity: 0.8;
}
/* If using Font Awesome for no-records icon: */
.no-records .fas.fa-bell-slash {
font-size: 3.5rem;
margin-bottom: 20px;
color: var(--mint-green);
opacity: 0.7;
}
.no-records p {
font-size: 1.1rem;
font-family: var(--font-body);
font-weight: 600;
color: var(--text-dark);
}
/* Notification Dropdown Styles (assuming this is for a navbar dropdown or similar) */
/* These are kept minimal as the main focus was the page content */
.notification-dropdown {
width: 350px; /* Wider for more content */
padding: 0;
max-height: 450px;
overflow-y: auto;
border-radius: var(--border-radius-small);
box-shadow: 0 5px 25px rgba(0,0,0,0.1);
background-color: var(--clean-white);
}
.notification-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background-color: var(--pale-yellow); /* Light yellow header */
border-bottom: 1px solid #f0e0d0;
}
.notification-header h5 {
margin:0;
font-family: var(--font-title);
font-weight: 600;
color: var(--text-dark);
}
.mark-all-read { /* Link in dropdown header */
font-size: 0.8rem;
color: var(--coral-pink);
font-weight: 600;
text-decoration: none;
}
.mark-all-read:hover {
text-decoration: underline;
}
.notification-items {
max-height: 300px;
overflow-y: auto;
}
.notification-item {
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
transition: background-color 0.2s ease;
}
.notification-item:last-child {
border-bottom: none;
}
.notification-item:hover {
background-color: var(--mint-green-light, #e6f7f0); /* Very light mint on hover */
}
.notification-item.unread {
background-color: #fff8f0; /* Very light orange/yellow for unread in dropdown */
border-left: 3px solid var(--bright-orange);
padding-left: 12px;
}
.notification-item .notification-content h6 { /* Assuming title in dropdown is h6 */
margin-bottom: 5px;
font-size: 0.9rem;
font-family: var(--font-title);
font-weight: 600;
color: var(--text-dark);
}
.notification-item .notification-text { /* Text snippet in dropdown */
font-size: 0.8rem;
color: var(--text-medium);
margin-bottom: 5px;
line-height: 1.4;
}
.notification-item .notification-time {
font-size: 0.75rem;
color: var(--text-light);
}
.view-all { /* Footer link in dropdown */
text-align: center;
font-weight: 600;
padding: 12px 15px;
display: block;
text-decoration: none;
color: var(--bright-orange);
background-color: #fffaf5;
transition: background-color 0.2s ease;
}
.view-all:hover {
background-color: var(--pale-yellow);
}
.no-notifications { /* Message in empty dropdown */
padding: 25px;
text-align: center;
color: var(--text-medium);
font-size: 0.9rem;
}
================================================================================
File: ./app/static/css/inventory-logs.css
================================================================================
/* 冰雪奇缘主题库存日志页面样式 */
@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);
}
}
================================================================================
File: ./app/static/css/user-roles.css
================================================================================
/* 角色管理页面样式 */
.roles-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
/* 页面标题 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.page-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}
.page-header .actions {
display: flex;
gap: 10px;
}
/* 角色列表 */
.role-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
/* 角色卡片 */
.role-card {
background-color: #f9f9fb;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.role-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.role-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.role-name {
font-size: 1.3rem;
color: #333;
margin: 0;
font-weight: 600;
}
.role-actions {
display: flex;
gap: 5px;
}
.role-actions .btn {
padding: 5px 8px;
color: #6c757d;
background: none;
border: none;
}
.role-actions .btn:hover {
color: #4c84ff;
}
.role-actions .btn-delete-role:hover {
color: #dc3545;
}
.role-description {
color: #555;
margin-bottom: 20px;
min-height: 50px;
line-height: 1.5;
}
.role-stats {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
color: #6c757d;
}
.stat-item {
display: flex;
align-items: center;
gap: 5px;
}
.stat-item i {
color: #4c84ff;
}
/* 角色标签 */
.role-badge {
display: inline-block;
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
.role-badge.admin {
background-color: #e3f2fd;
color: #1976d2;
}
.role-badge.user {
background-color: #e8f5e9;
color: #43a047;
}
.role-badge.custom {
background-color: #fff3e0;
color: #ef6c00;
}
/* 无数据提示 */
.no-data-message {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 50px 0;
color: #6c757d;
}
.no-data-message i {
font-size: 3rem;
margin-bottom: 15px;
opacity: 0.5;
}
/* 权限信息部分 */
.permissions-info {
margin-top: 30px;
}
.permissions-info h3 {
font-size: 1.4rem;
margin-bottom: 15px;
color: #333;
}
.permission-table {
width: 100%;
margin-bottom: 0;
}
.permission-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.permission-table td, .permission-table th {
padding: 12px 15px;
text-align: center;
}
.permission-table td:first-child {
text-align: left;
font-weight: 500;
}
.text-success {
color: #28a745;
}
.text-danger {
color: #dc3545;
}
/* 模态框样式 */
.modal-header {
background-color: #f8f9fa;
border-bottom: 1px solid #f0f0f0;
}
.modal-title {
font-weight: 600;
color: #333;
}
.modal-footer {
border-top: 1px solid #f0f0f0;
padding: 15px;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-group label {
font-weight: 500;
margin-bottom: 8px;
color: #333;
display: block;
}
.form-control {
height: auto;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control:focus {
border-color: #4c84ff;
box-shadow: 0 0 0 0.2rem rgba(76, 132, 255, 0.25);
}
/* 响应式调整 */
@media (max-width: 992px) {
.role-list {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 576px) {
.role-list {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
}
.page-header .actions {
margin-top: 15px;
}
}
/* 权限选择部分样式 */
.permissions-container {
max-height: 350px;
overflow-y: auto;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 15px;
background-color: #f8f9fa;
}
.permission-group {
margin-bottom: 20px;
}
.permission-group:last-child {
margin-bottom: 0;
}
.permission-group-title {
font-weight: 600;
color: #495057;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #dee2e6;
}
.permission-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.permission-item {
display: flex;
align-items: center;
}
.permission-checkbox {
margin-right: 8px;
}
.permission-name {
font-weight: 500;
margin-bottom: 5px;
display: block;
}
.permission-description {
font-size: 0.85rem;
color: #6c757d;
display: block;
}
.loading-permissions {
grid-column: 1 / -1;
text-align: center;
padding: 20px;
color: #6c757d;
}
/* 将模态框调整为大一点 */
.modal-lg {
max-width: 800px;
}
/* 权限项样式 */
.permission-item {
position: relative;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
}
.permission-item:hover {
background-color: #e9ecef;
}
.permission-item label {
display: flex;
flex-direction: column;
cursor: pointer;
margin-bottom: 0;
padding-left: 25px;
}
.permission-item input[type="checkbox"] {
position: absolute;
left: 12px;
top: 12px;
}
================================================================================
File: ./app/static/css/overdue_analysis.css
================================================================================
/* app/static/css/overdue_analysis.css */
/* 保留您现有的 CSS 样式 */
.stats-cards .stats-card {
border-left: 4px solid #007bff;
}
#current-overdue {
border-left-color: #dc3545;
}
#current-overdue .card-value {
color: #dc3545;
}
#returned-overdue {
border-left-color: #ffc107;
}
#returned-overdue .card-value {
color: #ffc107;
}
#overdue-rate {
border-left-color: #28a745;
}
#overdue-rate .card-value {
color: #28a745;
}
.chart-legend {
display: flex;
flex-wrap: wrap;
margin-top: 15px;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
font-size: 14px;
}
.legend-color {
width: 15px;
height: 15px;
border-radius: 4px;
margin-right: 5px;
}
/* 添加下面的 CSS 修复图表容器问题 */
.chart-container {
position: relative;
height: 400px; /* 固定高度 */
overflow: hidden; /* 防止内容溢出 */
margin-bottom: 30px;
}
.chart-container.half {
min-height: 350px;
max-height: 380px; /* 最大高度限制 */
}
.chart-container canvas {
max-height: 100%;
width: 100% !important;
height: 320px !important; /* 确保固定高度 */
}
/* 修复图表行的问题 */
.chart-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 30px;
align-items: stretch; /* 确保两个容器高度一致 */
}
.chart-row .half {
flex: 1 1 calc(50% - 10px);
min-width: 300px;
display: flex;
flex-direction: column;
}
/* 添加一个明确的底部间距,防止页面无限延伸 */
.statistics-container {
padding-bottom: 50px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.chart-row {
flex-direction: column;
}
.chart-container.half {
width: 100%;
margin-bottom: 20px;
}
}
================================================================================
File: ./app/static/css/announcement-detail.css
================================================================================
.announcement-detail-container {
padding: 20px;
max-width: 900px;
margin: 0 auto;
}
.page-header {
margin-bottom: 25px;
position: relative;
}
.back-link {
display: inline-block;
margin-bottom: 15px;
color: #6c757d;
text-decoration: none;
transition: color 0.2s;
}
.back-link:hover {
color: #007bff;
}
.page-header h1 {
margin-top: 0;
margin-bottom: 20px;
font-size: 2rem;
line-height: 1.3;
}
.announcement-meta {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 25px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
}
.meta-item {
display: flex;
align-items: center;
font-size: 0.95rem;
color: #6c757d;
}
.meta-item i {
margin-right: 8px;
}
.meta-item.pinned {
color: #dc3545;
font-weight: 500;
}
.announcement-content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
padding: 25px;
line-height: 1.7;
color: #333;
}
/* 内容中的富文本样式 */
.announcement-content h1,
.announcement-content h2,
.announcement-content h3 {
margin-top: 1.5em;
margin-bottom: 0.8em;
}
.announcement-content p {
margin-bottom: 1em;
}
.announcement-content img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 15px 0;
}
.announcement-content ul,
.announcement-content ol {
margin-bottom: 1em;
padding-left: 2em;
}
.announcement-content a {
color: #007bff;
}
.announcement-content blockquote {
border-left: 4px solid #e3e3e3;
padding-left: 15px;
color: #6c757d;
margin-left: 0;
margin-right: 0;
}
================================================================================
File: ./app/static/js/book-import.js
================================================================================
// 图书批量导入页面的JavaScript功能
document.addEventListener('DOMContentLoaded', function() {
// 显示选择的文件名
const fileInput = document.getElementById('file');
if (fileInput) {
fileInput.addEventListener('change', function() {
const fileName = this.value.split('\\').pop();
const label = document.querySelector('.custom-file-label');
if (label) {
label.textContent = fileName || '点击这里选择文件...';
// 添加有文件的类
if (fileName) {
this.parentElement.classList.add('has-file');
// 显示文件类型检查和预览信息
checkFileAndPreview(this.files[0]);
} else {
this.parentElement.classList.remove('has-file');
}
}
});
}
// 监听表单提交
const form = document.querySelector('form');
if (form) {
form.addEventListener('submit', function(e) {
const fileInput = document.getElementById('file');
if (!fileInput || !fileInput.files || !fileInput.files.length) {
e.preventDefault();
showMessage('请先选择要导入的Excel文件', 'warning');
return;
}
const importBtn = document.querySelector('.import-btn');
if (importBtn) {
importBtn.innerHTML = ' 正在导入...';
importBtn.disabled = true;
}
// 添加花朵飘落动画效果
addFallingElements(10);
// 设置超时处理,如果30秒后还没响应,提示用户
window.importTimeout = setTimeout(function() {
showMessage('导入处理时间较长,请耐心等待...', 'info');
}, 30000);
});
}
// 美化表单提交按钮的点击效果
const importBtn = document.querySelector('.import-btn');
if (importBtn) {
importBtn.addEventListener('click', function(e) {
// 按钮的点击效果已由表单提交事件处理
// 避免重复处理
if (!form || form.reportValidity() === false) {
e.preventDefault();
}
});
}
// 为浮动元素添加动画
initFloatingElements();
// 检查页面中的flash消息
checkFlashMessages();
});
// 检查页面中的flash消息
function checkFlashMessages() {
// Flask的flash消息通常会渲染为带有特定类的元素
const flashMessages = document.querySelectorAll('.alert');
if (flashMessages && flashMessages.length > 0) {
// 如果存在flash消息,说明页面是提交后重新加载的
// 恢复按钮状态
const importBtn = document.querySelector('.import-btn');
if (importBtn) {
importBtn.innerHTML = ' 开始导入';
importBtn.disabled = false;
}
// 清除可能的超时
if (window.importTimeout) {
clearTimeout(window.importTimeout);
}
}
}
// 检查文件类型并尝试预览
function checkFileAndPreview(file) {
if (!file) return;
// 检查文件类型
const validTypes = ['.xlsx', '.xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'];
let isValid = false;
validTypes.forEach(type => {
if (file.name.toLowerCase().endsWith(type) || file.type === type) {
isValid = true;
}
});
if (!isValid) {
showMessage('请选择有效的Excel文件 (.xlsx 或 .xls)', 'warning');
return;
}
// 显示文件准备就绪的消息
showMessage(`文件 "${file.name}" 已准备就绪,点击"开始导入"按钮继续。`, 'success');
}
// 显示提示消息
function showMessage(message, type = 'info') {
// 检查是否已有消息容器
let messageContainer = document.querySelector('.import-message');
if (!messageContainer) {
// 创建新的消息容器
messageContainer = document.createElement('div');
messageContainer.className = 'import-message animate__animated animate__fadeIn';
// 插入到按钮之后
const importBtn = document.querySelector('.import-btn');
if (importBtn && importBtn.parentNode) {
importBtn.parentNode.insertBefore(messageContainer, importBtn.nextSibling);
}
}
// 设置消息内容和样式
messageContainer.innerHTML = `
${message}
`;
// 如果是临时消息,设置自动消失
if (type !== 'danger') {
setTimeout(() => {
messageContainer.classList.add('animate__fadeOut');
setTimeout(() => {
if (messageContainer.parentNode) {
messageContainer.parentNode.removeChild(messageContainer);
}
}, 600);
}, 5000);
}
}
// 根据消息类型获取图标
function getIconForMessageType(type) {
switch (type) {
case 'success': return 'fa-check-circle';
case 'warning': return 'fa-exclamation-triangle';
case 'danger': return 'fa-times-circle';
default: return 'fa-info-circle';
}
}
// 初始化浮动元素
function initFloatingElements() {
const floatingElements = document.querySelectorAll('.snowflake, .flower');
floatingElements.forEach(element => {
const randomDuration = 15 + Math.random() * 20;
const randomDelay = Math.random() * 10;
element.style.animationDuration = `${randomDuration}s`;
element.style.animationDelay = `${randomDelay}s`;
});
}
// 添加花朵飘落效果
function addFallingElements(count) {
const container = document.querySelector('.import-container');
if (!container) return;
for (let i = 0; i < count; i++) {
const element = document.createElement('div');
element.className = 'falling-element animate__animated animate__fadeInDown';
// 随机选择花朵或雪花
const isFlower = Math.random() > 0.5;
element.classList.add(isFlower ? 'falling-flower' : 'falling-snowflake');
// 随机位置
const left = Math.random() * 100;
element.style.left = `${left}%`;
// 随机延迟
const delay = Math.random() * 2;
element.style.animationDelay = `${delay}s`;
// 随机大小
const size = 10 + Math.random() * 20;
element.style.width = `${size}px`;
element.style.height = `${size}px`;
container.appendChild(element);
// 动画结束后移除元素
setTimeout(() => {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}, 5000);
}
}
================================================================================
File: ./app/static/js/log-list.js
================================================================================
document.addEventListener('DOMContentLoaded', function() {
// 日期范围选择器逻辑
const dateRangeSelect = document.getElementById('date_range');
const dateRangeInputs = document.querySelector('.date-range-inputs');
if (dateRangeSelect && dateRangeInputs) {
dateRangeSelect.addEventListener('change', function() {
if (this.value === 'custom') {
dateRangeInputs.style.display = 'flex';
} else {
dateRangeInputs.style.display = 'none';
}
});
}
// 导出日志功能
const btnExport = document.getElementById('btnExport');
const exportModal = new bootstrap.Modal(document.getElementById('exportLogModal'));
const confirmExport = document.getElementById('confirmExport');
if (btnExport) {
btnExport.addEventListener('click', function() {
exportModal.show();
});
}
if (confirmExport) {
confirmExport.addEventListener('click', function() {
// 获取导出格式
const exportFormat = document.getElementById('exportFormat').value;
// 获取当前筛选条件
const userId = document.getElementById('user_id').value;
const action = document.getElementById('action').value;
const targetType = document.getElementById('target_type').value;
let startDate = '';
let endDate = '';
const dateRange = document.getElementById('date_range').value;
if (dateRange === 'custom') {
startDate = document.getElementById('start_date').value;
endDate = document.getElementById('end_date').value;
} else {
// 根据选择的日期范围计算日期
const today = new Date();
endDate = formatDate(today);
if (dateRange === '1') {
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
startDate = formatDate(yesterday);
} else if (dateRange === '7') {
const lastWeek = new Date(today);
lastWeek.setDate(lastWeek.getDate() - 7);
startDate = formatDate(lastWeek);
} else if (dateRange === '30') {
const lastMonth = new Date(today);
lastMonth.setDate(lastMonth.getDate() - 30);
startDate = formatDate(lastMonth);
}
}
// 显示加载提示
showAlert('info', '正在生成导出文件,请稍候...');
// 发送导出请求
fetch('/log/api/export', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userId || null,
action: action || null,
target_type: targetType || null,
start_date: startDate || null,
end_date: endDate || null,
format: exportFormat
})
})
.then(response => response.json())
.then(data => {
exportModal.hide();
if (data.success) {
showAlert('success', data.message);
// 处理文件下载
if (data.filedata && data.filename) {
// 解码Base64数据
const binaryData = atob(data.filedata);
// 转换为Blob
const blob = new Blob([new Uint8Array([...binaryData].map(char => char.charCodeAt(0)))],
{ type: data.filetype });
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = data.filename;
// 触发下载
document.body.appendChild(a);
a.click();
// 清理
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
} else {
showAlert('danger', data.message || '导出失败');
}
})
.catch(error => {
exportModal.hide();
showAlert('danger', '导出失败: ' + error.message);
});
});
}
// 清除日志功能
const btnClear = document.getElementById('btnClear');
const clearModal = new bootstrap.Modal(document.getElementById('clearLogModal'));
const confirmClear = document.getElementById('confirmClear');
if (btnClear) {
btnClear.addEventListener('click', function() {
clearModal.show();
});
}
if (confirmClear) {
confirmClear.addEventListener('click', function() {
const days = parseInt(document.getElementById('clearDays').value);
fetch('/log/api/clear', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ days: days })
})
.then(response => response.json())
.then(data => {
clearModal.hide();
if (data.success) {
showAlert('success', data.message);
// 2秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
showAlert('danger', data.message);
}
})
.catch(error => {
clearModal.hide();
showAlert('danger', '操作失败: ' + error.message);
});
});
}
// 辅助函数 - 格式化日期为 YYYY-MM-DD
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 辅助函数 - 显示提示框
function showAlert(type, message) {
// 移除之前的所有alert
const existingAlerts = document.querySelectorAll('.alert-floating');
existingAlerts.forEach(alert => alert.remove());
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show alert-floating`;
alertDiv.innerHTML = `
${message}
`;
document.body.appendChild(alertDiv);
// 添加CSS,如果还没有添加
if (!document.getElementById('alert-floating-style')) {
const style = document.createElement('style');
style.id = 'alert-floating-style';
style.textContent = `
.alert-floating {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
min-width: 300px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-left: 4px solid;
animation: slideIn 0.3s ease-out forwards;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.alert-floating i {
margin-right: 8px;
}
.alert-floating .close {
padding: 0.75rem;
}
`;
document.head.appendChild(style);
}
// 5秒后自动关闭
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.classList.add('fade');
setTimeout(() => alertDiv.remove(), 300);
}
}, 5000);
// 点击关闭按钮关闭
const closeButton = alertDiv.querySelector('.close');
if (closeButton) {
closeButton.addEventListener('click', function() {
alertDiv.classList.add('fade');
setTimeout(() => alertDiv.remove(), 300);
});
}
}
});
================================================================================
File: ./app/static/js/my_borrows.js
================================================================================
// my_borrows.js
document.addEventListener('DOMContentLoaded', function() {
// 归还图书功能
const returnButtons = document.querySelectorAll('.return-btn');
const returnModal = document.getElementById('returnModal');
const returnBookTitle = document.getElementById('returnBookTitle');
const confirmReturnButton = document.getElementById('confirmReturn');
let currentBorrowId = null;
returnButtons.forEach(button => {
button.addEventListener('click', function() {
const borrowId = this.getAttribute('data-id');
const bookTitle = this.getAttribute('data-title');
currentBorrowId = borrowId;
returnBookTitle.textContent = bookTitle;
// 使用 Bootstrap 的 jQuery 方法显示模态框
$('#returnModal').modal('show');
});
});
confirmReturnButton.addEventListener('click', function() {
if (!currentBorrowId) return;
// 发送归还请求
fetch(`/borrow/return/${currentBorrowId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(data => {
// 隐藏模态框
$('#returnModal').modal('hide');
if (data.success) {
// 显示成功消息
showAlert('success', data.message);
// 重新加载页面以更新借阅状态
setTimeout(() => window.location.reload(), 1500);
} else {
// 显示错误消息
showAlert('danger', data.message);
}
})
.catch(error => {
$('#returnModal').modal('hide');
showAlert('danger', '操作失败,请稍后重试');
console.error('Error:', error);
});
});
// 续借图书功能
const renewButtons = document.querySelectorAll('.renew-btn');
const renewModal = document.getElementById('renewModal');
const renewBookTitle = document.getElementById('renewBookTitle');
const confirmRenewButton = document.getElementById('confirmRenew');
renewButtons.forEach(button => {
button.addEventListener('click', function() {
const borrowId = this.getAttribute('data-id');
const bookTitle = this.getAttribute('data-title');
currentBorrowId = borrowId;
renewBookTitle.textContent = bookTitle;
// 使用 Bootstrap 的 jQuery 方法显示模态框
$('#renewModal').modal('show');
});
});
confirmRenewButton.addEventListener('click', function() {
if (!currentBorrowId) return;
// 发送续借请求
fetch(`/borrow/renew/${currentBorrowId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(data => {
// 隐藏模态框
$('#renewModal').modal('hide');
if (data.success) {
// 显示成功消息
showAlert('success', data.message);
// 重新加载页面以更新借阅状态
setTimeout(() => window.location.reload(), 1500);
} else {
// 显示错误消息
showAlert('danger', data.message);
}
})
.catch(error => {
$('#renewModal').modal('hide');
showAlert('danger', '操作失败,请稍后重试');
console.error('Error:', error);
});
});
// 显示提示消息
function showAlert(type, message) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show fixed-top mx-auto mt-3`;
alertDiv.style.maxWidth = '500px';
alertDiv.style.zIndex = '9999';
alertDiv.innerHTML = `
${message}
`;
document.body.appendChild(alertDiv);
// 3秒后自动消失
setTimeout(() => {
alertDiv.remove();
}, 3000);
}
});
================================================================================
File: ./app/static/js/announcement-form.js
================================================================================
// 公告编辑表单的Javascript
document.addEventListener('DOMContentLoaded', function() {
// 表单提交前验证
document.getElementById('announcementForm').addEventListener('submit', function(e) {
// 由于富文本内容在各页面单独处理,这里仅做一些通用表单验证
const title = document.getElementById('title').value.trim();
if (!title) {
e.preventDefault();
alert('请输入公告标题');
return false;
}
});
// 返回按钮处理
const cancelButton = document.querySelector('button[type="button"]');
if (cancelButton) {
cancelButton.addEventListener('click', function() {
// 如果有未保存内容,给出提示
if (formHasChanges()) {
if (!confirm('表单有未保存的内容,确定要离开吗?')) {
return;
}
}
history.back();
});
}
// 检测表单是否有变化
function formHasChanges() {
// 这里可以添加逻辑来检测表单内容是否有变化
// 简单实现:检查标题是否不为空
const title = document.getElementById('title').value.trim();
return title !== '';
}
});
================================================================================
File: ./app/static/js/user-edit.js
================================================================================
// 用户编辑页面交互
document.addEventListener('DOMContentLoaded', function() {
const passwordField = document.getElementById('password');
const confirmPasswordGroup = document.getElementById('confirmPasswordGroup');
const confirmPasswordField = document.getElementById('confirm_password');
const userEditForm = document.getElementById('userEditForm');
// 如果输入密码,显示确认密码字段
passwordField.addEventListener('input', function() {
if (this.value.trim() !== '') {
confirmPasswordGroup.style.display = 'block';
} else {
confirmPasswordGroup.style.display = 'none';
confirmPasswordField.value = '';
}
});
// 表单提交验证
userEditForm.addEventListener('submit', function(event) {
let valid = true;
// 清除之前的错误提示
const invalidFields = document.querySelectorAll('.is-invalid');
const feedbackElements = document.querySelectorAll('.invalid-feedback');
invalidFields.forEach(field => {
field.classList.remove('is-invalid');
});
feedbackElements.forEach(element => {
element.parentNode.removeChild(element);
});
// 邮箱格式验证
const emailField = document.getElementById('email');
if (emailField.value.trim() !== '') {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(emailField.value.trim())) {
showError(emailField, '请输入有效的邮箱地址');
valid = false;
}
}
// 手机号码格式验证
const phoneField = document.getElementById('phone');
if (phoneField.value.trim() !== '') {
const phonePattern = /^1[3456789]\d{9}$/;
if (!phonePattern.test(phoneField.value.trim())) {
showError(phoneField, '请输入有效的手机号码');
valid = false;
}
}
// 密码验证
if (passwordField.value.trim() !== '') {
if (passwordField.value.length < 6) {
showError(passwordField, '密码长度至少为6个字符');
valid = false;
}
if (passwordField.value !== confirmPasswordField.value) {
showError(confirmPasswordField, '两次输入的密码不一致');
valid = false;
}
}
if (!valid) {
event.preventDefault();
}
});
// 显示表单字段错误
function showError(field, message) {
field.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
feedback.innerText = message;
field.parentNode.appendChild(feedback);
}
// 处理表单提交后的成功反馈
const successAlert = document.querySelector('.alert-success');
if (successAlert) {
// 如果有成功消息,显示成功对话框
setTimeout(() => {
$('#successModal').modal('show');
}, 500);
}
});
================================================================================
File: ./app/static/js/book_ranking.js
================================================================================
// app/static/js/book_ranking.js
document.addEventListener('DOMContentLoaded', function() {
const timeRangeSelect = document.getElementById('time-range');
const limitSelect = document.getElementById('limit-count');
let rankingChart = null;
// 初始加载
loadRankingData();
// 添加事件监听器
timeRangeSelect.addEventListener('change', loadRankingData);
limitSelect.addEventListener('change', loadRankingData);
function loadRankingData() {
const timeRange = timeRangeSelect.value;
const limit = limitSelect.value;
// 显示加载状态
document.getElementById('ranking-table-body').innerHTML = `
正在打开书页... |
`;
// 调用API获取数据
fetch(`/statistics/api/book-ranking?time_range=${timeRange}&limit=${limit}`)
.then(response => response.json())
.then(data => {
// 更新表格
updateRankingTable(data);
// 更新图表
updateRankingChart(data);
})
.catch(error => {
console.error('加载排行数据失败:', error);
document.getElementById('ranking-table-body').innerHTML = `
| 加载数据失败,请稍后重试 |
`;
});
}
function updateRankingTable(data) {
const tableBody = document.getElementById('ranking-table-body');
if (data.length === 0) {
tableBody.innerHTML = `
| 暂无数据 |
`;
return;
}
let tableHtml = '';
data.forEach((book, index) => {
// 给每个单元格添加适当的类名以匹配CSS
tableHtml += `
| ${index + 1} |
|
${book.title} |
${book.author} |
${book.borrow_count} |
`;
});
tableBody.innerHTML = tableHtml;
}
function updateRankingChart(data) {
// 销毁旧图表
if (rankingChart) {
rankingChart.destroy();
}
if (data.length === 0) {
return;
}
// 准备图表数据
const labels = data.map(book => book.title);
const borrowCounts = data.map(book => book.borrow_count);
// 创建图表
const ctx = document.getElementById('ranking-chart').getContext('2d');
rankingChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '借阅次数',
data: borrowCounts,
backgroundColor: 'rgba(183, 110, 121, 0.6)', // 玫瑰金色调
borderColor: 'rgba(140, 45, 90, 1)', // 浆果红
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '借阅次数',
font: {
family: "'Open Sans', sans-serif",
size: 13
},
color: '#5D5053'
},
ticks: {
color: '#8A797C',
font: {
family: "'Open Sans', sans-serif"
}
},
grid: {
color: 'rgba(211, 211, 211, 0.3)'
}
},
x: {
title: {
display: true,
text: '图书',
font: {
family: "'Open Sans', sans-serif",
size: 13
},
color: '#5D5053'
},
ticks: {
color: '#8A797C',
font: {
family: "'Open Sans', sans-serif"
}
},
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
},
title: {
display: true,
text: '热门图书借阅排行',
font: {
family: "'Playfair Display', serif",
size: 16,
weight: 'bold'
},
color: '#B76E79'
}
}
}
});
}
});
================================================================================
File: ./app/static/js/user-roles.js
================================================================================
// 角色管理页面交互
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const addRoleBtn = document.getElementById('addRoleBtn');
const roleModal = $('#roleModal');
const roleForm = document.getElementById('roleForm');
const roleIdInput = document.getElementById('roleId');
const roleNameInput = document.getElementById('roleName');
const roleDescriptionInput = document.getElementById('roleDescription');
const saveRoleBtn = document.getElementById('saveRoleBtn');
const deleteModal = $('#deleteModal');
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
let roleIdToDelete = null;
// 加载角色用户统计
fetchRoleUserCounts();
// 初始化时加载权限列表
loadPermissions();
// 添加角色按钮点击事件
if (addRoleBtn) {
addRoleBtn.addEventListener('click', function() {
// 重置表单
roleIdInput.value = '';
roleNameInput.value = '';
roleDescriptionInput.value = '';
// 更新模态框标题
document.getElementById('roleModalLabel').textContent = '添加角色';
// 启用所有权限复选框
document.querySelectorAll('.permission-checkbox').forEach(checkbox => {
checkbox.checked = false;
checkbox.disabled = false;
});
// 隐藏系统角色警告
const systemRoleAlert = document.getElementById('systemRoleAlert');
if (systemRoleAlert) {
systemRoleAlert.style.display = 'none';
}
// 显示模态框
roleModal.modal('show');
});
}
// 编辑角色按钮点击事件
const editButtons = document.querySelectorAll('.btn-edit-role');
editButtons.forEach(button => {
button.addEventListener('click', function() {
const roleCard = this.closest('.role-card');
const roleId = roleCard.getAttribute('data-id');
const roleName = roleCard.querySelector('.role-name').textContent;
let roleDescription = roleCard.querySelector('.role-description').textContent;
// 移除"暂无描述"文本
if (roleDescription.trim() === '暂无描述') {
roleDescription = '';
}
// 填充表单
roleIdInput.value = roleId;
roleNameInput.value = roleName;
roleDescriptionInput.value = roleDescription.trim();
// 更新模态框标题
document.getElementById('roleModalLabel').textContent = '编辑角色';
// 加载角色权限
loadRolePermissions(roleId);
// 显示模态框
roleModal.modal('show');
});
});
// 删除角色按钮点击事件
const deleteButtons = document.querySelectorAll('.btn-delete-role');
deleteButtons.forEach(button => {
button.addEventListener('click', function() {
const roleCard = this.closest('.role-card');
roleIdToDelete = roleCard.getAttribute('data-id');
// 显示确认删除模态框
deleteModal.modal('show');
});
});
// 保存角色按钮点击事件
if (saveRoleBtn) {
saveRoleBtn.addEventListener('click', function() {
if (!roleNameInput.value.trim()) {
showAlert('角色名称不能为空', 'error');
return;
}
// 收集选中的权限ID
const permissionIds = [];
document.querySelectorAll('.permission-checkbox:checked:not(:disabled)').forEach(checkbox => {
permissionIds.push(parseInt(checkbox.value));
});
const roleData = {
id: roleIdInput.value || null,
role_name: roleNameInput.value.trim(),
description: roleDescriptionInput.value.trim() || null,
permissions: permissionIds
};
saveRole(roleData);
});
}
// 确认删除按钮点击事件
if (confirmDeleteBtn) {
confirmDeleteBtn.addEventListener('click', function() {
if (roleIdToDelete) {
deleteRole(roleIdToDelete);
deleteModal.modal('hide');
}
});
}
// 加载权限列表
function loadPermissions() {
// 获取权限容器
const bookPermissions = document.getElementById('book-permissions');
const userPermissions = document.getElementById('user-permissions');
const borrowPermissions = document.getElementById('borrow-permissions');
const systemPermissions = document.getElementById('system-permissions');
if (!bookPermissions) return; // 如果元素不存在就退出
// 设置加载中状态
bookPermissions.innerHTML = ' 加载权限中...
';
userPermissions.innerHTML = '';
borrowPermissions.innerHTML = '';
systemPermissions.innerHTML = '';
// 获取权限数据
fetch('/user/permissions', {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
if (data.success) {
// 清除加载中状态
bookPermissions.innerHTML = '';
// 按分组整理权限
const permissionGroups = {
book: [], // 图书相关权限
user: [], // 用户相关权限
borrow: [], // 借阅相关权限
system: [] // 系统相关权限
};
// 定义权限分组映射
const permGroupMap = {
'manage_books': 'book',
'manage_categories': 'book',
'import_export_books': 'book',
'manage_users': 'user',
'manage_roles': 'user',
'manage_borrows': 'borrow',
'manage_overdue': 'borrow',
// 系统相关权限
'manage_announcements': 'system',
'manage_inventory': 'system',
'view_logs': 'system',
'view_statistics': 'system'
};
// 根据映射表分组
data.permissions.forEach(perm => {
const group = permGroupMap[perm.code] || 'system';
permissionGroups[group].push(perm);
});
// 渲染各组权限
renderPermissionGroup(bookPermissions, permissionGroups.book);
renderPermissionGroup(userPermissions, permissionGroups.user);
renderPermissionGroup(borrowPermissions, permissionGroups.borrow);
renderPermissionGroup(systemPermissions, permissionGroups.system);
} else {
bookPermissions.innerHTML = '加载权限失败
';
}
})
.catch(error => {
console.error('Error:', error);
bookPermissions.innerHTML = '加载权限失败,请刷新页面重试
';
});
}
// 渲染权限组
function renderPermissionGroup(container, permissions) {
if (permissions.length === 0) {
container.innerHTML = '暂无相关权限
';
return;
}
let html = '';
permissions.forEach(perm => {
html += `
`;
});
container.innerHTML = html;
}
// 加载角色的权限
function loadRolePermissions(roleId) {
if (!roleId) return;
// 先清空所有已选权限
document.querySelectorAll('.permission-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
// 如果是系统内置角色,显示警告并禁用权限选择
const isSystemRole = (roleId == 1 || roleId == 2);
const systemRoleAlert = document.getElementById('systemRoleAlert');
const permissionCheckboxes = document.querySelectorAll('.permission-checkbox');
if (systemRoleAlert) {
systemRoleAlert.style.display = isSystemRole ? 'block' : 'none';
}
// 管理员角色自动选中所有权限并禁用
if (roleId == 1) { // 管理员
permissionCheckboxes.forEach(checkbox => {
checkbox.checked = true;
checkbox.disabled = true;
});
return;
} else if (roleId == 2) { // 普通用户,只有基本权限
permissionCheckboxes.forEach(checkbox => {
checkbox.checked = false;
checkbox.disabled = true; // 普通用户权限不可修改
});
// 获取普通用户已分配的权限
fetch(`/user/role/${roleId}/permissions`, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
permissionCheckboxes.forEach(checkbox => {
// 如果权限ID在返回的列表中,则选中
checkbox.checked = data.permissions.includes(parseInt(checkbox.value));
});
}
});
return;
}
// 为自定义角色加载并选中权限
fetch(`/user/role/${roleId}/permissions`, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
permissionCheckboxes.forEach(checkbox => {
// 启用所有复选框
checkbox.disabled = false;
// 如果权限ID在返回的列表中,则选中
checkbox.checked = data.permissions.includes(parseInt(checkbox.value));
});
}
});
}
// 保存角色
function saveRole(roleData) {
// 显示加载状态
saveRoleBtn.innerHTML = ' 保存中...';
saveRoleBtn.disabled = true;
fetch('/user/role/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(roleData)
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
// 恢复按钮状态
saveRoleBtn.innerHTML = '保存';
saveRoleBtn.disabled = false;
if (data.success) {
// 关闭模态框
roleModal.modal('hide');
showAlert(data.message, 'success');
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
showAlert(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
// 恢复按钮状态
saveRoleBtn.innerHTML = '保存';
saveRoleBtn.disabled = false;
showAlert('保存失败,请稍后重试', 'error');
});
}
// 删除角色
function deleteRole(roleId) {
// 显示加载状态
confirmDeleteBtn.innerHTML = ' 删除中...';
confirmDeleteBtn.disabled = true;
fetch(`/user/role/delete/${roleId}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
// 恢复按钮状态
confirmDeleteBtn.innerHTML = '确认删除';
confirmDeleteBtn.disabled = false;
if (data.success) {
showAlert(data.message, 'success');
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
showAlert(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
// 恢复按钮状态
confirmDeleteBtn.innerHTML = '确认删除';
confirmDeleteBtn.disabled = false;
showAlert('删除失败,请稍后重试', 'error');
});
}
// 获取角色用户数量
function fetchRoleUserCounts() {
const roleCards = document.querySelectorAll('.role-card');
roleCards.forEach(card => {
const roleId = card.getAttribute('data-id');
const countElement = document.getElementById(`userCount-${roleId}`);
if (countElement) {
// 设置"加载中"状态
countElement.innerHTML = '加载中...';
// 定义默认的角色用户数量 (用于API不可用时)
const defaultCounts = {
'1': 1, // 管理员
'2': 5, // 普通用户
};
// 尝试获取用户数量
fetch(`/user/role/${roleId}/count`)
.then(response => {
if (!response.ok) {
throw new Error('API不可用');
}
return response.json();
})
.then(data => {
// 检查返回数据的success属性
if (data.success) {
countElement.textContent = data.count;
} else {
throw new Error(data.message || 'API返回错误');
}
})
.catch(error => {
console.warn(`获取角色ID=${roleId}的用户数量失败:`, error);
// 使用默认值
const defaultCounts = {
'1': 1, // 固定值而非随机值
'2': 5,
'3': 3
};
countElement.textContent = defaultCounts[roleId] || 0;
// 静默失败 - 不向用户显示错误,只在控制台记录
});
}
});
}
// 显示通知
function showAlert(message, type) {
// 检查是否已有通知元素
let alertBox = document.querySelector('.alert-box');
if (!alertBox) {
alertBox = document.createElement('div');
alertBox.className = 'alert-box';
document.body.appendChild(alertBox);
}
// 创建新的通知
const alert = document.createElement('div');
alert.className = `alert alert-${type === 'success' ? 'success' : 'danger'} fade-in`;
alert.innerHTML = message;
// 添加到通知框中
alertBox.appendChild(alert);
// 自动关闭
setTimeout(() => {
alert.classList.add('fade-out');
setTimeout(() => {
alertBox.removeChild(alert);
}, 500);
}, 3000);
}
// 添加CSS样式以支持通知动画
function addAlertStyles() {
if (!document.getElementById('alert-styles')) {
const style = document.createElement('style');
style.id = 'alert-styles';
style.textContent = `
.alert-box {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 350px;
}
.alert {
margin-bottom: 10px;
padding: 15px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
opacity: 0;
transition: opacity 0.3s ease;
}
.fade-in {
opacity: 1;
}
.fade-out {
opacity: 0;
}
`;
document.head.appendChild(style);
}
}
// 添加通知样式
addAlertStyles();
});
================================================================================
File: ./app/static/js/user-add.js
================================================================================
document.addEventListener('DOMContentLoaded', function() {
// 密码显示/隐藏切换
const togglePasswordButtons = document.querySelectorAll('.toggle-password');
togglePasswordButtons.forEach(button => {
button.addEventListener('click', function() {
const passwordField = this.previousElementSibling;
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField.setAttribute('type', type);
// 更改图标
const icon = this.querySelector('i');
if (type === 'text') {
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
});
// 密码一致性检查
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm_password');
const passwordMatchMessage = document.getElementById('password-match-message');
function checkPasswordMatch() {
if (confirmPasswordInput.value === '') {
passwordMatchMessage.textContent = '';
passwordMatchMessage.className = 'form-text';
return;
}
if (passwordInput.value === confirmPasswordInput.value) {
passwordMatchMessage.textContent = '密码匹配';
passwordMatchMessage.className = 'form-text text-success';
} else {
passwordMatchMessage.textContent = '密码不匹配';
passwordMatchMessage.className = 'form-text text-danger';
}
}
passwordInput.addEventListener('input', checkPasswordMatch);
confirmPasswordInput.addEventListener('input', checkPasswordMatch);
// 发送邮箱验证码
const sendVerificationCodeButton = document.getElementById('sendVerificationCode');
const emailInput = document.getElementById('email');
sendVerificationCodeButton.addEventListener('click', function() {
const email = emailInput.value.trim();
// 验证邮箱格式
if (!email) {
alert('请输入邮箱地址');
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('请输入有效的邮箱地址');
return;
}
// 禁用按钮,防止重复点击
this.disabled = true;
this.textContent = '发送中...';
// 发送AJAX请求
fetch('/user/send_verification_code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: email }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 成功发送,开始倒计时
startCountdown(this);
alert(data.message);
} else {
// 发送失败,恢复按钮状态
this.disabled = false;
this.textContent = '发送验证码';
alert(data.message || '发送失败,请稍后重试');
}
})
.catch(error => {
console.error('Error:', error);
this.disabled = false;
this.textContent = '发送验证码';
alert('发送失败,请稍后重试');
});
});
// 验证码倒计时(60秒)
function startCountdown(button) {
let seconds = 60;
const originalText = '发送验证码';
const countdownInterval = setInterval(() => {
seconds--;
button.textContent = `${seconds}秒后重发`;
if (seconds <= 0) {
clearInterval(countdownInterval);
button.textContent = originalText;
button.disabled = false;
}
}, 1000);
}
// 表单提交前验证
const addUserForm = document.getElementById('addUserForm');
addUserForm.addEventListener('submit', function(event) {
// 检查密码是否匹配
if (passwordInput.value !== confirmPasswordInput.value) {
event.preventDefault();
alert('两次输入的密码不匹配,请重新输入');
return;
}
// 如果还有其他前端验证,可以继续添加
});
// 自动填充用户名为昵称的默认值
const usernameInput = document.getElementById('username');
const nicknameInput = document.getElementById('nickname');
usernameInput.addEventListener('change', function() {
// 只有当昵称字段为空时才自动填充
if (!nicknameInput.value) {
nicknameInput.value = this.value;
}
});
});
================================================================================
File: ./app/static/js/browse.js
================================================================================
// 图书浏览页面脚本
$(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(' 处理中...');
$.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(`
`);
// 创建借阅成功动画
const successOverlay = $('借阅成功
');
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); }
}
`;
$('
404
噢!页面不见了~
抱歉,您要找的页面似乎藏起来了,或者从未存在过。
请检查您输入的网址是否正确,或者回到首页继续浏览吧!
返回首页