32225 lines
966 KiB
Plaintext
32225 lines
966 KiB
Plaintext
|
||
================================================================================
|
||
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>© 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">×</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">×</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() {
|
||