Book_system/code_collection.txt
2025-05-17 15:34:28 +08:00

32225 lines
966 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

================================================================================
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', '<br>'))
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"""
<html>
<body>
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e1e1e1; border-radius: 5px;">
<h2 style="color: #4a89dc;">图书管理系统 - 邮箱验证</h2>
<p>您好,</p>
<p>感谢您注册图书管理系统,您的验证码是:</p>
<div style="background-color: #f5f5f5; padding: 10px; border-radius: 5px; text-align: center; font-size: 24px; letter-spacing: 5px; font-weight: bold; margin: 20px 0;">
{verification_code}
</div>
<p>该验证码将在10分钟内有效请勿将验证码分享给他人。</p>
<p>如果您没有请求此验证码,请忽略此邮件。</p>
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e1e1e1; font-size: 12px; color: #888;">
<p>此邮件为系统自动发送,请勿回复。</p>
<p>&copy; 2025 图书管理系统</p>
</div>
</div>
</body>
</html>
"""
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'<Category {self.name}>'
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'<Book {self.title}>'
================================================================================
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'<BorrowRecord {self.id}>'
================================================================================
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'<InventoryLog {self.id}>'
================================================================================
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,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M95,50A45,45,0,1,1,50,5,45,45,0,0,1,95,50Z" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="10"/></svg>') repeat;
background-size: 80px 80px;
opacity: 0.4;
}
.header-content {
position: relative;
z-index: 2;
}
.page-header h1 {
color: #fff;
margin: 0;
font-size: 2.5rem;
font-weight: 300;
letter-spacing: 1px;
text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
}
.header-icon {
margin-right: 15px;
color: #fff;
}
.subtitle {
color: #fff;
margin-top: 10px;
font-size: 1.1rem;
font-weight: 300;
opacity: 0.9;
}
/* 搜索框样式 */
.search-card {
background: #fff;
border-radius: var(--border-radius);
padding: var(--card-padding);
margin-bottom: 30px;
box-shadow: var(--card-shadow);
border-top: 4px solid var(--primary-color);
}
.search-form {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.search-input-group {
display: flex;
flex: 1;
min-width: 300px;
}
.search-input-container {
position: relative;
flex: 1;
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--light-text);
}
.search-input {
width: 100%;
padding: 12px 15px 12px 40px;
border: 1px solid #e3e3e3;
border-radius: var(--border-radius) 0 0 var(--border-radius);
font-size: 1rem;
transition: var(--transition);
outline: none;
}
.search-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px var(--primary-light);
}
.search-button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 12px 25px;
font-size: 1rem;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
cursor: pointer;
transition: var(--transition);
}
.search-button:hover {
background-color: var(--primary-dark);
}
.log-button {
background-color: #fff;
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 11px 20px;
border-radius: var(--border-radius);
text-decoration: none;
font-size: 0.95rem;
transition: var(--transition);
display: inline-flex;
align-items: center;
gap: 8px;
}
.log-button:hover {
background-color: var(--primary-light);
color: var(--primary-dark);
}
/* 表格样式 */
.table-container {
background: #fff;
border-radius: var(--border-radius);
padding: var(--card-padding);
margin-bottom: 30px;
box-shadow: var(--card-shadow);
overflow: hidden;
}
.inventory-table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
.inventory-table th {
background-color: var(--primary-light);
color: var(--primary-dark);
padding: 15px;
text-align: left;
font-weight: 600;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.5px;
}
.inventory-table tr {
border-bottom: 1px solid #f3f3f3;
transition: var(--transition);
}
.inventory-table tr:last-child {
border-bottom: none;
}
.inventory-table tr:hover {
background-color: #f9f9f9;
}
.inventory-table td {
padding: 15px;
vertical-align: middle;
}
.book-title {
font-weight: 500;
color: var(--text-color);
}
.book-author {
color: var(--light-text);
font-style: italic;
}
/* 库存和状态标签样式 */
.stock-badge, .status-badge {
display: inline-block;
padding: 6px 12px;
border-radius: 50px;
font-size: 0.85rem;
font-weight: 500;
text-align: center;
min-width: 60px;
}
.stock-high {
background-color: var(--success-color);
color: #fff;
}
.stock-medium {
background-color: var(--warning-color);
color: #8a7800;
}
.stock-low {
background-color: var(--danger-color);
color: #fff;
}
.status-active {
background-color: #d9f5e6;
color: #2a9d5c;
}
.status-inactive {
background-color: #ffe8e8;
color: #e35555;
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 8px;
}
.btn-adjust, .btn-view {
padding: 8px 12px;
border-radius: var(--border-radius);
text-decoration: none;
font-size: 0.85rem;
display: inline-flex;
align-items: center;
gap: 5px;
transition: var(--transition);
}
.btn-adjust {
background-color: var(--primary-light);
color: var(--primary-dark);
border: 1px solid var(--primary-color);
}
.btn-adjust:hover {
background-color: var(--primary-color);
color: white;
}
.btn-view {
background-color: var(--secondary-color);
color: #3573b5;
border: 1px solid #8ab9e3;
}
.btn-view:hover {
background-color: #8ab9e3;
color: white;
}
/* 分页样式 */
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 30px;
}
.pagination {
display: flex;
list-style: none;
padding: 0;
margin: 0;
gap: 5px;
}
.page-item {
display: inline-block;
}
.page-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 40px;
padding: 0 15px;
border-radius: var(--border-radius);
background-color: #fff;
color: var(--text-color);
text-decoration: none;
transition: var(--transition);
border: 1px solid #e3e3e3;
}
.page-item.active .page-link {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.page-item:not(.active) .page-link:hover {
background-color: var(--primary-light);
color: var(--primary-dark);
border-color: var(--primary-light);
}
.page-item.disabled .page-link {
background-color: #f5f5f5;
color: #aaa;
cursor: not-allowed;
}
/* 响应式调整 */
@media (max-width: 992px) {
.inventory-container {
padding: 15px;
}
.page-header {
padding: 30px 20px;
}
.page-header h1 {
font-size: 2rem;
}
}
@media (max-width: 768px) {
.search-form {
flex-direction: column;
align-items: stretch;
}
.log-button {
text-align: center;
}
.page-header h1 {
font-size: 1.8rem;
}
.table-container {
overflow-x: auto;
}
.inventory-table {
min-width: 800px;
}
.action-buttons {
flex-direction: column;
}
.btn-adjust, .btn-view {
text-align: center;
}
}
@media (max-width: 576px) {
.page-header {
padding: 25px 15px;
}
.page-header h1 {
font-size: 1.5rem;
}
.subtitle {
font-size: 1rem;
}
.pagination .page-link {
min-width: 35px;
height: 35px;
padding: 0 10px;
font-size: 0.9rem;
}
}
================================================================================
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 = '<i class="fas fa-spinner fa-spin"></i> 正在导入...';
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 = '<i class="fas fa-upload"></i> 开始导入';
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 = `
<div class="alert alert-${type} mt-3">
<i class="fas ${getIconForMessageType(type)}"></i> ${message}
</div>
`;
// 如果是临时消息,设置自动消失
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 = `
<i class="fas fa-${type === 'success' ? 'check-circle' :
type === 'danger' ? 'exclamation-circle' :
type === 'info' ? 'info-circle' : 'bell'}"></i>
${message}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
`;
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}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
`;
document.body.appendChild(alertDiv);
// 3秒后自动消失
setTimeout(() => {
alertDiv.remove();
}, 3000);
}
});
================================================================================
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 = `
<tr class="loading-row">
<td colspan="5"><div class="loading-animation"><span>正在打开书页</span><span class="dot-animation">...</span></div></td>
</tr>
`;
// 调用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 = `
<tr class="error-row">
<td colspan="5">加载数据失败,请稍后重试</td>
</tr>
`;
});
}
function updateRankingTable(data) {
const tableBody = document.getElementById('ranking-table-body');
if (data.length === 0) {
tableBody.innerHTML = `
<tr class="no-data-row">
<td colspan="5">暂无数据</td>
</tr>
`;
return;
}
let tableHtml = '';
data.forEach((book, index) => {
// 给每个单元格添加适当的类名以匹配CSS
tableHtml += `
<tr>
<td class="rank">${index + 1}</td>
<td class="book-cover">
<img src="${book.cover_url || '/static/images/book-placeholder.jpg'}" alt="${book.title}">
</td>
<td class="book-title-cell"><span class="book-title">${book.title}</span></td>
<td class="author">${book.author}</td>
<td><span class="borrow-count">${book.borrow_count}</span></td>
</tr>
`;
});
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 = '<div class="loading-permissions"><i class="fas fa-spinner fa-spin"></i> 加载权限中...</div>';
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 = '<div class="text-danger">加载权限失败</div>';
}
})
.catch(error => {
console.error('Error:', error);
bookPermissions.innerHTML = '<div class="text-danger">加载权限失败,请刷新页面重试</div>';
});
}
// 渲染权限组
function renderPermissionGroup(container, permissions) {
if (permissions.length === 0) {
container.innerHTML = '<div class="text-muted">暂无相关权限</div>';
return;
}
let html = '';
permissions.forEach(perm => {
html += `
<div class="permission-item">
<input type="checkbox" id="perm_${perm.id}" class="permission-checkbox" value="${perm.id}">
<label for="perm_${perm.id}">
<span class="permission-name">${perm.name}</span>
<span class="permission-description">${perm.description || '无描述'}</span>
</label>
</div>
`;
});
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 = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 保存中...';
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 = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 删除中...';
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 = '<small>加载中...</small>';
// 定义默认的角色用户数量 (用于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() {