user
This commit is contained in:
		
							parent
							
								
									0c1d1b0d19
								
							
						
					
					
						commit
						29009ef7de
					
				@ -1,12 +1,16 @@
 | 
			
		||||
from flask import Flask, render_template, session, g, Markup
 | 
			
		||||
from flask_login import LoginManager
 | 
			
		||||
from app.models.user import db, User
 | 
			
		||||
from app.controllers.user import user_bp
 | 
			
		||||
from app.controllers.book import book_bp
 | 
			
		||||
from app.controllers.borrow import borrow_bp
 | 
			
		||||
from flask_login import LoginManager, current_user
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
login_manager = LoginManager()
 | 
			
		||||
 | 
			
		||||
def create_app():
 | 
			
		||||
 | 
			
		||||
def create_app(config=None):
 | 
			
		||||
    app = Flask(__name__)
 | 
			
		||||
 | 
			
		||||
    # 配置应用
 | 
			
		||||
@ -32,6 +36,14 @@ def create_app():
 | 
			
		||||
    # 初始化数据库
 | 
			
		||||
    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))
 | 
			
		||||
 | 
			
		||||
    # 注册蓝图
 | 
			
		||||
    app.register_blueprint(user_bp, url_prefix='/user')
 | 
			
		||||
    app.register_blueprint(book_bp, url_prefix='/book')
 | 
			
		||||
@ -105,9 +117,9 @@ def create_app():
 | 
			
		||||
 | 
			
		||||
    @app.route('/')
 | 
			
		||||
    def index():
 | 
			
		||||
        if not g.user:
 | 
			
		||||
        if not current_user.is_authenticated:
 | 
			
		||||
            return render_template('login.html')
 | 
			
		||||
        return render_template('index.html', current_user=g.user)
 | 
			
		||||
        return render_template('index.html')  # 无需传递current_user,Flask-Login自动提供
 | 
			
		||||
 | 
			
		||||
    @app.errorhandler(404)
 | 
			
		||||
    def page_not_found(e):
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,8 @@ def book_list():
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
    per_page = request.args.get('per_page', 10, type=int)
 | 
			
		||||
 | 
			
		||||
    query = Book.query
 | 
			
		||||
    # 只显示状态为1的图书(未下架的图书)
 | 
			
		||||
    query = Book.query.filter_by(status=1)
 | 
			
		||||
 | 
			
		||||
    # 搜索功能
 | 
			
		||||
    search = request.args.get('search', '')
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,9 @@ import logging
 | 
			
		||||
from functools import wraps
 | 
			
		||||
import time
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from app.services.user_service import UserService
 | 
			
		||||
from flask_login import login_user, logout_user, current_user, login_required
 | 
			
		||||
from app.models.user import User
 | 
			
		||||
 | 
			
		||||
# 创建蓝图
 | 
			
		||||
user_bp = Blueprint('user', __name__)
 | 
			
		||||
@ -46,11 +49,13 @@ class VerificationStore:
 | 
			
		||||
verification_codes = VerificationStore()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def login_required(f):
 | 
			
		||||
# 添加管理员权限检查装饰器
 | 
			
		||||
def admin_required(f):
 | 
			
		||||
    @wraps(f)
 | 
			
		||||
    def decorated_function(*args, **kwargs):
 | 
			
		||||
        if 'user_id' not in session:
 | 
			
		||||
            return redirect(url_for('user.login'))
 | 
			
		||||
        if not current_user.is_authenticated or current_user.role_id != 1:
 | 
			
		||||
            flash('您没有管理员权限', 'error')
 | 
			
		||||
            return redirect(url_for('index'))
 | 
			
		||||
        return f(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    return decorated_function
 | 
			
		||||
@ -58,7 +63,10 @@ def login_required(f):
 | 
			
		||||
 | 
			
		||||
@user_bp.route('/login', methods=['GET', 'POST'])
 | 
			
		||||
def login():
 | 
			
		||||
    # 保持原代码不变
 | 
			
		||||
    # 如果用户已经登录,直接重定向到首页
 | 
			
		||||
    if current_user.is_authenticated:
 | 
			
		||||
        return redirect(url_for('index'))
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        username = request.form.get('username')
 | 
			
		||||
        password = request.form.get('password')
 | 
			
		||||
@ -76,26 +84,30 @@ def login():
 | 
			
		||||
        if user.status == 0:
 | 
			
		||||
            return render_template('login.html', error='账号已被禁用,请联系管理员')
 | 
			
		||||
 | 
			
		||||
        # 登录成功,保存用户信息到会话
 | 
			
		||||
        session['user_id'] = user.id
 | 
			
		||||
        # 使用 Flask-Login 的 login_user 函数
 | 
			
		||||
        login_user(user, remember=remember_me)
 | 
			
		||||
 | 
			
		||||
        # 这些session信息仍然可以保留,但不再用于认证
 | 
			
		||||
        session['username'] = user.username
 | 
			
		||||
        session['role_id'] = user.role_id
 | 
			
		||||
 | 
			
		||||
        if remember_me:
 | 
			
		||||
            # 设置会话过期时间为7天
 | 
			
		||||
            session.permanent = True
 | 
			
		||||
        # 获取登录后要跳转的页面
 | 
			
		||||
        next_page = request.args.get('next')
 | 
			
		||||
        if not next_page or not next_page.startswith('/'):
 | 
			
		||||
            next_page = url_for('index')
 | 
			
		||||
 | 
			
		||||
        # 记录登录日志(可选)
 | 
			
		||||
        # log_user_action('用户登录')
 | 
			
		||||
 | 
			
		||||
        # 重定向到首页
 | 
			
		||||
        return redirect(url_for('index'))
 | 
			
		||||
        # 重定向到首页或其他请求的页面
 | 
			
		||||
        return redirect(next_page)
 | 
			
		||||
 | 
			
		||||
    return render_template('login.html')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@user_bp.route('/register', methods=['GET', 'POST'])
 | 
			
		||||
def register():
 | 
			
		||||
    # 如果用户已登录,重定向到首页
 | 
			
		||||
    if current_user.is_authenticated:
 | 
			
		||||
        return redirect(url_for('index'))
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        username = request.form.get('username')
 | 
			
		||||
        email = request.form.get('email')
 | 
			
		||||
@ -147,11 +159,9 @@ def register():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@user_bp.route('/logout')
 | 
			
		||||
@login_required
 | 
			
		||||
def logout():
 | 
			
		||||
    # 清除会话数据
 | 
			
		||||
    session.pop('user_id', None)
 | 
			
		||||
    session.pop('username', None)
 | 
			
		||||
    session.pop('role_id', None)
 | 
			
		||||
    logout_user()
 | 
			
		||||
    return redirect(url_for('user.login'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -179,3 +189,194 @@ def send_verification_code():
 | 
			
		||||
        return jsonify({'success': True, 'message': '验证码已发送'})
 | 
			
		||||
    else:
 | 
			
		||||
        return jsonify({'success': False, 'message': '邮件发送失败,请稍后重试'})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 用户管理列表
 | 
			
		||||
@user_bp.route('/manage')
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def user_list():
 | 
			
		||||
    page = request.args.get('page', 1, type=int)
 | 
			
		||||
    search = request.args.get('search', '')
 | 
			
		||||
    status = request.args.get('status', type=int)
 | 
			
		||||
    role_id = request.args.get('role_id', type=int)
 | 
			
		||||
 | 
			
		||||
    pagination = UserService.get_users(
 | 
			
		||||
        page=page,
 | 
			
		||||
        per_page=10,
 | 
			
		||||
        search_query=search,
 | 
			
		||||
        status=status,
 | 
			
		||||
        role_id=role_id
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    roles = UserService.get_all_roles()
 | 
			
		||||
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'user/list.html',
 | 
			
		||||
        pagination=pagination,
 | 
			
		||||
        search=search,
 | 
			
		||||
        status=status,
 | 
			
		||||
        role_id=role_id,
 | 
			
		||||
        roles=roles
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 用户详情/编辑页面
 | 
			
		||||
@user_bp.route('/edit/<int:user_id>', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def user_edit(user_id):
 | 
			
		||||
    user = UserService.get_user_by_id(user_id)
 | 
			
		||||
    if not user:
 | 
			
		||||
        flash('用户不存在', 'error')
 | 
			
		||||
        return redirect(url_for('user.user_list'))
 | 
			
		||||
 | 
			
		||||
    roles = UserService.get_all_roles()
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        data = {
 | 
			
		||||
            'email': request.form.get('email'),
 | 
			
		||||
            'phone': request.form.get('phone'),
 | 
			
		||||
            'nickname': request.form.get('nickname'),
 | 
			
		||||
            'role_id': int(request.form.get('role_id')),
 | 
			
		||||
            'status': int(request.form.get('status')),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        password = request.form.get('password')
 | 
			
		||||
        if password:
 | 
			
		||||
            data['password'] = password
 | 
			
		||||
 | 
			
		||||
        success, message = UserService.update_user(user_id, data)
 | 
			
		||||
        if success:
 | 
			
		||||
            flash(message, 'success')
 | 
			
		||||
            return redirect(url_for('user.user_list'))
 | 
			
		||||
        else:
 | 
			
		||||
            flash(message, 'error')
 | 
			
		||||
 | 
			
		||||
    return render_template('user/edit.html', user=user, roles=roles)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 用户状态管理API
 | 
			
		||||
@user_bp.route('/status/<int:user_id>', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def user_status(user_id):
 | 
			
		||||
    data = request.get_json()
 | 
			
		||||
    status = data.get('status')
 | 
			
		||||
 | 
			
		||||
    if status is None or status not in [0, 1]:
 | 
			
		||||
        return jsonify({'success': False, 'message': '无效的状态值'})
 | 
			
		||||
 | 
			
		||||
    # 不能修改自己的状态
 | 
			
		||||
    if user_id == current_user.id:
 | 
			
		||||
        return jsonify({'success': False, 'message': '不能修改自己的状态'})
 | 
			
		||||
 | 
			
		||||
    success, message = UserService.change_user_status(user_id, status)
 | 
			
		||||
    return jsonify({'success': success, 'message': message})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 用户删除API
 | 
			
		||||
@user_bp.route('/delete/<int:user_id>', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def user_delete(user_id):
 | 
			
		||||
    # 不能删除自己
 | 
			
		||||
    if user_id == current_user.id:
 | 
			
		||||
        return jsonify({'success': False, 'message': '不能删除自己的账号'})
 | 
			
		||||
 | 
			
		||||
    success, message = UserService.delete_user(user_id)
 | 
			
		||||
    return jsonify({'success': success, 'message': message})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 个人中心页面
 | 
			
		||||
@user_bp.route('/profile', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def user_profile():
 | 
			
		||||
    user = current_user
 | 
			
		||||
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        data = {
 | 
			
		||||
            'email': request.form.get('email'),
 | 
			
		||||
            'phone': request.form.get('phone'),
 | 
			
		||||
            'nickname': request.form.get('nickname')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        current_password = request.form.get('current_password')
 | 
			
		||||
        new_password = request.form.get('new_password')
 | 
			
		||||
        confirm_password = request.form.get('confirm_password')
 | 
			
		||||
 | 
			
		||||
        # 如果用户想要修改密码
 | 
			
		||||
        if current_password and new_password:
 | 
			
		||||
            if not user.check_password(current_password):
 | 
			
		||||
                flash('当前密码不正确', 'error')
 | 
			
		||||
                return render_template('user/profile.html', user=user)
 | 
			
		||||
 | 
			
		||||
            if new_password != confirm_password:
 | 
			
		||||
                flash('两次输入的新密码不匹配', 'error')
 | 
			
		||||
                return render_template('user/profile.html', user=user)
 | 
			
		||||
 | 
			
		||||
            data['password'] = new_password
 | 
			
		||||
 | 
			
		||||
        success, message = UserService.update_user(user.id, data)
 | 
			
		||||
        if success:
 | 
			
		||||
            flash(message, 'success')
 | 
			
		||||
        else:
 | 
			
		||||
            flash(message, 'error')
 | 
			
		||||
 | 
			
		||||
    return render_template('user/profile.html', user=user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 角色管理页面
 | 
			
		||||
@user_bp.route('/roles', methods=['GET'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def role_list():
 | 
			
		||||
    roles = UserService.get_all_roles()
 | 
			
		||||
    return render_template('user/roles.html', roles=roles)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 创建/编辑角色API
 | 
			
		||||
@user_bp.route('/role/save', methods=['POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def role_save():
 | 
			
		||||
    data = request.get_json()
 | 
			
		||||
    role_id = data.get('id')
 | 
			
		||||
    role_name = data.get('role_name')
 | 
			
		||||
    description = data.get('description')
 | 
			
		||||
 | 
			
		||||
    if not role_name:
 | 
			
		||||
        return jsonify({'success': False, 'message': '角色名不能为空'})
 | 
			
		||||
 | 
			
		||||
    if role_id:  # 更新
 | 
			
		||||
        success, message = UserService.update_role(role_id, role_name, description)
 | 
			
		||||
    else:  # 创建
 | 
			
		||||
        success, message = UserService.create_role(role_name, description)
 | 
			
		||||
 | 
			
		||||
    return jsonify({'success': success, 'message': message})
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
@user_bp.route('/api/role/<int:role_id>/user-count')
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def get_role_user_count(role_id):
 | 
			
		||||
    count = User.query.filter_by(role_id=role_id).count()
 | 
			
		||||
    return jsonify({'count': count})
 | 
			
		||||
"""
 | 
			
		||||
@user_bp.route('/user/role/<int:role_id>/count', methods=['GET'])
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def get_role_user_count(role_id):
 | 
			
		||||
    """获取指定角色的用户数量"""
 | 
			
		||||
    try:
 | 
			
		||||
        count = User.query.filter_by(role_id=role_id).count()
 | 
			
		||||
        return jsonify({
 | 
			
		||||
            'success': True,
 | 
			
		||||
            'count': count
 | 
			
		||||
        })
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        return jsonify({
 | 
			
		||||
            'success': False,
 | 
			
		||||
            'message': f"查询失败: {str(e)}",
 | 
			
		||||
            'count': 0
 | 
			
		||||
        }), 500
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
from werkzeug.security import generate_password_hash, check_password_hash
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from flask_login import UserMixin
 | 
			
		||||
 | 
			
		||||
db = SQLAlchemy()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(db.Model):
 | 
			
		||||
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)
 | 
			
		||||
@ -27,6 +27,9 @@ class User(db.Model):
 | 
			
		||||
        self.nickname = nickname
 | 
			
		||||
        self.role_id = role_id
 | 
			
		||||
 | 
			
		||||
    def is_active(self):
 | 
			
		||||
        return self.status == 1
 | 
			
		||||
 | 
			
		||||
    def set_password(self, password):
 | 
			
		||||
        """设置密码,使用哈希加密"""
 | 
			
		||||
        self.password = generate_password_hash(password)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,163 @@
 | 
			
		||||
# app/services/user_service.py
 | 
			
		||||
 | 
			
		||||
from app.models.user import User, Role, db
 | 
			
		||||
from sqlalchemy import or_
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserService:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_users(page=1, per_page=10, search_query=None, status=None, role_id=None):
 | 
			
		||||
        """
 | 
			
		||||
        获取用户列表,支持分页、搜索和过滤
 | 
			
		||||
        """
 | 
			
		||||
        query = User.query
 | 
			
		||||
 | 
			
		||||
        # 搜索条件
 | 
			
		||||
        if search_query:
 | 
			
		||||
            query = query.filter(or_(
 | 
			
		||||
                User.username.like(f'%{search_query}%'),
 | 
			
		||||
                User.email.like(f'%{search_query}%'),
 | 
			
		||||
                User.nickname.like(f'%{search_query}%'),
 | 
			
		||||
                User.phone.like(f'%{search_query}%')
 | 
			
		||||
            ))
 | 
			
		||||
 | 
			
		||||
        # 状态过滤
 | 
			
		||||
        if status is not None:
 | 
			
		||||
            query = query.filter(User.status == status)
 | 
			
		||||
 | 
			
		||||
        # 角色过滤
 | 
			
		||||
        if role_id is not None:
 | 
			
		||||
            query = query.filter(User.role_id == role_id)
 | 
			
		||||
 | 
			
		||||
        # 分页
 | 
			
		||||
        pagination = query.order_by(User.id.desc()).paginate(
 | 
			
		||||
            page=page, per_page=per_page, error_out=False
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return pagination
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_user_by_id(user_id):
 | 
			
		||||
        """通过ID获取用户"""
 | 
			
		||||
        return User.query.get(user_id)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def update_user(user_id, data):
 | 
			
		||||
        """更新用户信息"""
 | 
			
		||||
        user = User.query.get(user_id)
 | 
			
		||||
        if not user:
 | 
			
		||||
            return False, "用户不存在"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # 更新可编辑字段
 | 
			
		||||
            if 'email' in data:
 | 
			
		||||
                # 检查邮箱是否已被其他用户使用
 | 
			
		||||
                existing = User.query.filter(User.email == data['email'], User.id != user_id).first()
 | 
			
		||||
                if existing:
 | 
			
		||||
                    return False, "邮箱已被使用"
 | 
			
		||||
                user.email = data['email']
 | 
			
		||||
 | 
			
		||||
            if 'phone' in data:
 | 
			
		||||
                # 检查手机号是否已被其他用户使用
 | 
			
		||||
                existing = User.query.filter(User.phone == data['phone'], User.id != user_id).first()
 | 
			
		||||
                if existing:
 | 
			
		||||
                    return False, "手机号已被使用"
 | 
			
		||||
                user.phone = data['phone']
 | 
			
		||||
 | 
			
		||||
            if 'nickname' in data and data['nickname']:
 | 
			
		||||
                user.nickname = data['nickname']
 | 
			
		||||
 | 
			
		||||
            # 只有管理员可以修改这些字段
 | 
			
		||||
            if 'role_id' in data:
 | 
			
		||||
                user.role_id = data['role_id']
 | 
			
		||||
 | 
			
		||||
            if 'status' in data:
 | 
			
		||||
                user.status = data['status']
 | 
			
		||||
 | 
			
		||||
            if 'password' in data and data['password']:
 | 
			
		||||
                user.set_password(data['password'])
 | 
			
		||||
 | 
			
		||||
            user.updated_at = datetime.now()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            return True, "用户信息更新成功"
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            db.session.rollback()
 | 
			
		||||
            return False, f"更新失败: {str(e)}"
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def change_user_status(user_id, status):
 | 
			
		||||
        """变更用户状态 (启用/禁用)"""
 | 
			
		||||
        user = User.query.get(user_id)
 | 
			
		||||
        if not user:
 | 
			
		||||
            return False, "用户不存在"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            user.status = status
 | 
			
		||||
            user.updated_at = datetime.now()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            status_text = "启用" if status == 1 else "禁用"
 | 
			
		||||
            return True, f"用户已{status_text}"
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            db.session.rollback()
 | 
			
		||||
            return False, f"状态变更失败: {str(e)}"
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def delete_user(user_id):
 | 
			
		||||
        """删除用户 (软删除,将状态设为-1)"""
 | 
			
		||||
        user = User.query.get(user_id)
 | 
			
		||||
        if not user:
 | 
			
		||||
            return False, "用户不存在"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            user.status = -1  # 软删除,设置状态为-1
 | 
			
		||||
            user.updated_at = datetime.now()
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            return True, "用户已删除"
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            db.session.rollback()
 | 
			
		||||
            return False, f"删除失败: {str(e)}"
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_all_roles():
 | 
			
		||||
        """获取所有角色"""
 | 
			
		||||
        return Role.query.all()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def create_role(role_name, description=None):
 | 
			
		||||
        """创建新角色"""
 | 
			
		||||
        existing = Role.query.filter_by(role_name=role_name).first()
 | 
			
		||||
        if existing:
 | 
			
		||||
            return False, "角色名已存在"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            role = Role(role_name=role_name, description=description)
 | 
			
		||||
            db.session.add(role)
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            return True, "角色创建成功"
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            db.session.rollback()
 | 
			
		||||
            return False, f"创建失败: {str(e)}"
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def update_role(role_id, role_name, description=None):
 | 
			
		||||
        """更新角色信息"""
 | 
			
		||||
        role = Role.query.get(role_id)
 | 
			
		||||
        if not role:
 | 
			
		||||
            return False, "角色不存在"
 | 
			
		||||
 | 
			
		||||
        # 检查角色名是否已被使用
 | 
			
		||||
        existing = Role.query.filter(Role.role_name == role_name, Role.id != role_id).first()
 | 
			
		||||
        if existing:
 | 
			
		||||
            return False, "角色名已存在"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            role.role_name = role_name
 | 
			
		||||
            if description is not None:
 | 
			
		||||
                role.description = description
 | 
			
		||||
            db.session.commit()
 | 
			
		||||
            return True, "角色更新成功"
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            db.session.rollback()
 | 
			
		||||
            return False, f"更新失败: {str(e)}"
 | 
			
		||||
							
								
								
									
										240
									
								
								app/static/css/user-edit.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								app/static/css/user-edit.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,240 @@
 | 
			
		||||
/* 用户编辑页面样式 */
 | 
			
		||||
.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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								app/static/css/user-list.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								app/static/css/user-list.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,244 @@
 | 
			
		||||
/* 用户列表页面样式 */
 | 
			
		||||
.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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										375
									
								
								app/static/css/user-profile.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								app/static/css/user-profile.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,375 @@
 | 
			
		||||
/* 用户个人中心页面样式 */
 | 
			
		||||
.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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										253
									
								
								app/static/css/user-roles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								app/static/css/user-roles.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,253 @@
 | 
			
		||||
/* 角色管理页面样式 */
 | 
			
		||||
.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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -40,7 +40,7 @@ $(document).ready(function() {
 | 
			
		||||
  // 处理删除图书
 | 
			
		||||
  let bookIdToDelete = null;
 | 
			
		||||
 | 
			
		||||
  $('.delete-btn').click(function(e) {
 | 
			
		||||
  $('.delete-book').click(function(e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    bookIdToDelete = $(this).data('id');
 | 
			
		||||
    const bookTitle = $(this).data('title');
 | 
			
		||||
@ -60,21 +60,28 @@ $(document).ready(function() {
 | 
			
		||||
          // 显示成功消息
 | 
			
		||||
          showNotification(response.message, 'success');
 | 
			
		||||
          // 移除图书卡片
 | 
			
		||||
          $(`.book-card[data-id="${bookIdToDelete}"]`).fadeOut(300, function() {
 | 
			
		||||
            $(this).remove();
 | 
			
		||||
          });
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            location.reload();
 | 
			
		||||
          }, 800);
 | 
			
		||||
            if ($('.book-card').length === 0) {
 | 
			
		||||
              location.reload(); // 如果没有图书了,刷新页面显示"无图书"提示
 | 
			
		||||
            }
 | 
			
		||||
          }, 500);
 | 
			
		||||
        } else {
 | 
			
		||||
          $('#deleteModal').modal('hide');
 | 
			
		||||
          showNotification(response.message, 'error');
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      error: function() {
 | 
			
		||||
        $('#deleteModal').modal('hide');
 | 
			
		||||
        showNotification('删除操作失败,请稍后重试', 'error');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 处理借阅图书
 | 
			
		||||
  $('.borrow-btn').click(function(e) {
 | 
			
		||||
  $('.borrow-book').click(function(e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    const bookId = $(this).data('id');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										91
									
								
								app/static/js/user-edit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								app/static/js/user-edit.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
			
		||||
// 用户编辑页面交互
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										124
									
								
								app/static/js/user-list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								app/static/js/user-list.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
			
		||||
// 用户列表页面交互
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
    // 处理状态切换按钮
 | 
			
		||||
    const toggleStatusButtons = document.querySelectorAll('.toggle-status');
 | 
			
		||||
    toggleStatusButtons.forEach(button => {
 | 
			
		||||
        button.addEventListener('click', function() {
 | 
			
		||||
            const userId = this.getAttribute('data-id');
 | 
			
		||||
            const newStatus = parseInt(this.getAttribute('data-status'));
 | 
			
		||||
            const statusText = newStatus === 1 ? '启用' : '禁用';
 | 
			
		||||
 | 
			
		||||
            if (confirm(`确定要${statusText}该用户吗?`)) {
 | 
			
		||||
                toggleUserStatus(userId, newStatus);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 处理删除按钮
 | 
			
		||||
    const deleteButtons = document.querySelectorAll('.delete-user');
 | 
			
		||||
    const deleteModal = $('#deleteModal');
 | 
			
		||||
    let userIdToDelete = null;
 | 
			
		||||
 | 
			
		||||
    deleteButtons.forEach(button => {
 | 
			
		||||
        button.addEventListener('click', function() {
 | 
			
		||||
            userIdToDelete = this.getAttribute('data-id');
 | 
			
		||||
            deleteModal.modal('show');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 确认删除按钮
 | 
			
		||||
    document.getElementById('confirmDelete').addEventListener('click', function() {
 | 
			
		||||
        if (userIdToDelete) {
 | 
			
		||||
            deleteUser(userIdToDelete);
 | 
			
		||||
            deleteModal.modal('hide');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 自动提交表单的下拉菜单
 | 
			
		||||
    const autoSubmitSelects = document.querySelectorAll('select[name="status"], select[name="role_id"]');
 | 
			
		||||
    autoSubmitSelects.forEach(select => {
 | 
			
		||||
        select.addEventListener('change', function() {
 | 
			
		||||
            this.closest('form').submit();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 切换用户状态
 | 
			
		||||
function toggleUserStatus(userId, status) {
 | 
			
		||||
    fetch(`/user/status/${userId}`, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
            'X-Requested-With': 'XMLHttpRequest'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ status: status })
 | 
			
		||||
    })
 | 
			
		||||
    .then(response => response.json())
 | 
			
		||||
    .then(data => {
 | 
			
		||||
        if (data.success) {
 | 
			
		||||
            showAlert(data.message, 'success');
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }, 1500);
 | 
			
		||||
        } else {
 | 
			
		||||
            showAlert(data.message, 'error');
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    .catch(error => {
 | 
			
		||||
        console.error('Error:', error);
 | 
			
		||||
        showAlert('操作失败,请稍后重试', 'error');
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除用户
 | 
			
		||||
function deleteUser(userId) {
 | 
			
		||||
    fetch(`/user/delete/${userId}`, {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
            'X-Requested-With': 'XMLHttpRequest'
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    .then(response => response.json())
 | 
			
		||||
    .then(data => {
 | 
			
		||||
        if (data.success) {
 | 
			
		||||
            showAlert(data.message, 'success');
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }, 1500);
 | 
			
		||||
        } else {
 | 
			
		||||
            showAlert(data.message, 'error');
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    .catch(error => {
 | 
			
		||||
        console.error('Error:', error);
 | 
			
		||||
        showAlert('操作失败,请稍后重试', 'error');
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 显示通知
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										275
									
								
								app/static/js/user-profile.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								app/static/js/user-profile.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,275 @@
 | 
			
		||||
// 用户个人中心页面交互
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
    // 获取表单对象
 | 
			
		||||
    const profileForm = document.getElementById('profileForm');
 | 
			
		||||
    const passwordForm = document.getElementById('passwordForm');
 | 
			
		||||
 | 
			
		||||
    // 表单验证逻辑
 | 
			
		||||
    if (profileForm) {
 | 
			
		||||
        profileForm.addEventListener('submit', function(event) {
 | 
			
		||||
            let valid = true;
 | 
			
		||||
 | 
			
		||||
            // 清除之前的错误提示
 | 
			
		||||
            clearValidationErrors();
 | 
			
		||||
 | 
			
		||||
            // 验证邮箱
 | 
			
		||||
            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 (!valid) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 密码修改表单验证
 | 
			
		||||
    if (passwordForm) {
 | 
			
		||||
        passwordForm.addEventListener('submit', function(event) {
 | 
			
		||||
            let valid = true;
 | 
			
		||||
 | 
			
		||||
            // 清除之前的错误提示
 | 
			
		||||
            clearValidationErrors();
 | 
			
		||||
 | 
			
		||||
            // 验证当前密码
 | 
			
		||||
            const currentPasswordField = document.getElementById('current_password');
 | 
			
		||||
            if (currentPasswordField.value.trim() === '') {
 | 
			
		||||
                showError(currentPasswordField, '请输入当前密码');
 | 
			
		||||
                valid = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 验证新密码
 | 
			
		||||
            const newPasswordField = document.getElementById('new_password');
 | 
			
		||||
            if (newPasswordField.value.trim() === '') {
 | 
			
		||||
                showError(newPasswordField, '请输入新密码');
 | 
			
		||||
                valid = false;
 | 
			
		||||
            } else if (newPasswordField.value.length < 6) {
 | 
			
		||||
                showError(newPasswordField, '密码长度至少为6个字符');
 | 
			
		||||
                valid = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 验证确认密码
 | 
			
		||||
            const confirmPasswordField = document.getElementById('confirm_password');
 | 
			
		||||
            if (confirmPasswordField.value.trim() === '') {
 | 
			
		||||
                showError(confirmPasswordField, '请确认新密码');
 | 
			
		||||
                valid = false;
 | 
			
		||||
            } else if (confirmPasswordField.value !== newPasswordField.value) {
 | 
			
		||||
                showError(confirmPasswordField, '两次输入的密码不一致');
 | 
			
		||||
                valid = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!valid) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取用户统计数据
 | 
			
		||||
    fetchUserStats();
 | 
			
		||||
 | 
			
		||||
    // 获取用户活动记录
 | 
			
		||||
    const activityFilter = document.getElementById('activityFilter');
 | 
			
		||||
    if (activityFilter) {
 | 
			
		||||
        // 初始加载
 | 
			
		||||
        fetchUserActivities('all');
 | 
			
		||||
 | 
			
		||||
        // 监听过滤器变化
 | 
			
		||||
        activityFilter.addEventListener('change', function() {
 | 
			
		||||
            fetchUserActivities(this.value);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理URL中的tab参数
 | 
			
		||||
    const urlParams = new URLSearchParams(window.location.search);
 | 
			
		||||
    const tabParam = urlParams.get('tab');
 | 
			
		||||
    if (tabParam) {
 | 
			
		||||
        const tabElement = document.getElementById(`${tabParam}-tab`);
 | 
			
		||||
        if (tabElement) {
 | 
			
		||||
            $('#profileTabs a[href="#' + tabParam + '"]').tab('show');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 清除表单验证错误
 | 
			
		||||
    function clearValidationErrors() {
 | 
			
		||||
        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);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 显示错误消息
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取用户统计数据
 | 
			
		||||
    function fetchUserStats() {
 | 
			
		||||
        // 这里使用虚拟数据,实际应用中应当从后端获取
 | 
			
		||||
        // fetch('/api/user/stats')
 | 
			
		||||
        // .then(response => response.json())
 | 
			
		||||
        // .then(data => {
 | 
			
		||||
        //     updateUserStats(data);
 | 
			
		||||
        // });
 | 
			
		||||
 | 
			
		||||
        // 模拟数据
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            const mockData = {
 | 
			
		||||
                borrow: 2,
 | 
			
		||||
                returned: 15,
 | 
			
		||||
                overdue: 0
 | 
			
		||||
            };
 | 
			
		||||
            updateUserStats(mockData);
 | 
			
		||||
        }, 500);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 更新用户统计显示
 | 
			
		||||
    function updateUserStats(data) {
 | 
			
		||||
        const borrowCount = document.getElementById('borrowCount');
 | 
			
		||||
        const returnedCount = document.getElementById('returnedCount');
 | 
			
		||||
        const overdueCount = document.getElementById('overdueCount');
 | 
			
		||||
 | 
			
		||||
        if (borrowCount) borrowCount.textContent = data.borrow;
 | 
			
		||||
        if (returnedCount) returnedCount.textContent = data.returned;
 | 
			
		||||
        if (overdueCount) overdueCount.textContent = data.overdue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取用户活动记录
 | 
			
		||||
    function fetchUserActivities(type) {
 | 
			
		||||
        const timelineContainer = document.getElementById('activityTimeline');
 | 
			
		||||
        if (!timelineContainer) return;
 | 
			
		||||
 | 
			
		||||
        // 显示加载中
 | 
			
		||||
        timelineContainer.innerHTML = `
 | 
			
		||||
            <div class="timeline-loading">
 | 
			
		||||
                <div class="spinner-border text-primary" role="status">
 | 
			
		||||
                    <span class="sr-only">Loading...</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <p>加载中...</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
 | 
			
		||||
        // 实际应用中应当从后端获取
 | 
			
		||||
        // fetch(`/api/user/activities?type=${type}`)
 | 
			
		||||
        // .then(response => response.json())
 | 
			
		||||
        // .then(data => {
 | 
			
		||||
        //     renderActivityTimeline(data, timelineContainer);
 | 
			
		||||
        // });
 | 
			
		||||
 | 
			
		||||
        // 模拟数据
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            const mockActivities = [
 | 
			
		||||
                {
 | 
			
		||||
                    id: 1,
 | 
			
		||||
                    type: 'login',
 | 
			
		||||
                    title: '系统登录',
 | 
			
		||||
                    details: '成功登录系统',
 | 
			
		||||
                    time: '2023-04-28 15:30:22',
 | 
			
		||||
                    ip: '192.168.1.1'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id: 2,
 | 
			
		||||
                    type: 'borrow',
 | 
			
		||||
                    title: '借阅图书',
 | 
			
		||||
                    details: '借阅《JavaScript高级编程》',
 | 
			
		||||
                    time: '2023-04-27 11:45:10',
 | 
			
		||||
                    book_id: 101
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id: 3,
 | 
			
		||||
                    type: 'return',
 | 
			
		||||
                    title: '归还图书',
 | 
			
		||||
                    details: '归还《Python数据分析》',
 | 
			
		||||
                    time: '2023-04-26 09:15:33',
 | 
			
		||||
                    book_id: 95
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id: 4,
 | 
			
		||||
                    type: 'login',
 | 
			
		||||
                    title: '系统登录',
 | 
			
		||||
                    details: '成功登录系统',
 | 
			
		||||
                    time: '2023-04-25 08:22:15',
 | 
			
		||||
                    ip: '192.168.1.1'
 | 
			
		||||
                }
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            // 根据筛选条件过滤活动
 | 
			
		||||
            let filteredActivities = mockActivities;
 | 
			
		||||
            if (type !== 'all') {
 | 
			
		||||
                filteredActivities = mockActivities.filter(activity => activity.type === type);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            renderActivityTimeline(filteredActivities, timelineContainer);
 | 
			
		||||
        }, 800);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 渲染活动时间线
 | 
			
		||||
    function renderActivityTimeline(activities, container) {
 | 
			
		||||
        if (!activities || activities.length === 0) {
 | 
			
		||||
            container.innerHTML = '<div class="text-center p-4">暂无活动记录</div>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let timelineHTML = '';
 | 
			
		||||
 | 
			
		||||
        activities.forEach((activity, index) => {
 | 
			
		||||
            let iconClass = 'fas fa-info';
 | 
			
		||||
 | 
			
		||||
            if (activity.type === 'login') {
 | 
			
		||||
                iconClass = 'fas fa-sign-in-alt';
 | 
			
		||||
            } else if (activity.type === 'borrow') {
 | 
			
		||||
                iconClass = 'fas fa-book';
 | 
			
		||||
            } else if (activity.type === 'return') {
 | 
			
		||||
                iconClass = 'fas fa-undo';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const isLast = index === activities.length - 1;
 | 
			
		||||
 | 
			
		||||
            timelineHTML += `
 | 
			
		||||
                <div class="timeline-item ${isLast ? 'last' : ''} timeline-type-${activity.type}">
 | 
			
		||||
                    <div class="timeline-icon">
 | 
			
		||||
                        <i class="${iconClass}"></i>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="timeline-content">
 | 
			
		||||
                        <div class="timeline-header">
 | 
			
		||||
                            <h5 class="timeline-title">${activity.title}</h5>
 | 
			
		||||
                            <div class="timeline-time">${activity.time}</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="timeline-details">
 | 
			
		||||
                            ${activity.details}
 | 
			
		||||
                            ${activity.ip ? `<div class="text-muted small">IP: ${activity.ip}</div>` : ''}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            `;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        container.innerHTML = timelineHTML;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										301
									
								
								app/static/js/user-roles.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								app/static/js/user-roles.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,301 @@
 | 
			
		||||
// 角色管理页面交互
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
    // 添加角色按钮点击事件
 | 
			
		||||
    if (addRoleBtn) {
 | 
			
		||||
        addRoleBtn.addEventListener('click', function() {
 | 
			
		||||
            // 重置表单
 | 
			
		||||
            roleIdInput.value = '';
 | 
			
		||||
            roleNameInput.value = '';
 | 
			
		||||
            roleDescriptionInput.value = '';
 | 
			
		||||
 | 
			
		||||
            // 更新模态框标题
 | 
			
		||||
            document.getElementById('roleModalLabel').textContent = '添加角色';
 | 
			
		||||
 | 
			
		||||
            // 显示模态框
 | 
			
		||||
            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 = '编辑角色';
 | 
			
		||||
 | 
			
		||||
            // 显示模态框
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const roleData = {
 | 
			
		||||
                id: roleIdInput.value || null,
 | 
			
		||||
                role_name: roleNameInput.value.trim(),
 | 
			
		||||
                description: roleDescriptionInput.value.trim() || null
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            saveRole(roleData);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 确认删除按钮点击事件
 | 
			
		||||
    if (confirmDeleteBtn) {
 | 
			
		||||
        confirmDeleteBtn.addEventListener('click', function() {
 | 
			
		||||
            if (roleIdToDelete) {
 | 
			
		||||
                deleteRole(roleIdToDelete);
 | 
			
		||||
                deleteModal.modal('hide');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 保存角色
 | 
			
		||||
    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() {
 | 
			
		||||
        if (!document.getElementById('alert-styles')) {
 | 
			
		||||
            const style = document.createElement('style');
 | 
			
		||||
            style.id = 'alert-styles';
 | 
			
		||||
            style.textContent = `
 | 
			
		||||
                .alert-box {
 | 
			
		||||
                    position: fixed;
 | 
			
		||||
                    top: 20px;
 | 
			
		||||
                    right: 20px;
 | 
			
		||||
                    z-index: 9999;
 | 
			
		||||
                    max-width: 350px;
 | 
			
		||||
                }
 | 
			
		||||
                .alert {
 | 
			
		||||
                    margin-bottom: 10px;
 | 
			
		||||
                    padding: 15px;
 | 
			
		||||
                    border-radius: 4px;
 | 
			
		||||
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
 | 
			
		||||
                    opacity: 0;
 | 
			
		||||
                    transition: opacity 0.3s ease;
 | 
			
		||||
                }
 | 
			
		||||
                .fade-in {
 | 
			
		||||
                    opacity: 1;
 | 
			
		||||
                }
 | 
			
		||||
                .fade-out {
 | 
			
		||||
                    opacity: 0;
 | 
			
		||||
                }
 | 
			
		||||
            `;
 | 
			
		||||
            document.head.appendChild(style);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 添加通知样式
 | 
			
		||||
    addAlertStyles();
 | 
			
		||||
});
 | 
			
		||||
@ -5,45 +5,157 @@
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>页面未找到 - 图书管理系统</title>
 | 
			
		||||
    <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
 | 
			
		||||
    <style>
 | 
			
		||||
        body {
 | 
			
		||||
            font-family: 'Poppins', sans-serif;
 | 
			
		||||
            background-color: #fff5f7;
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            height: 100vh;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-container {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            padding: 50px 20px;
 | 
			
		||||
            max-width: 650px;
 | 
			
		||||
            padding: 40px;
 | 
			
		||||
            border-radius: 20px;
 | 
			
		||||
            background: #ffffff;
 | 
			
		||||
            box-shadow: 0 10px 30px rgba(252, 162, 193, 0.2);
 | 
			
		||||
            position: relative;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-container::before {
 | 
			
		||||
            content: "";
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 6px;
 | 
			
		||||
            background: linear-gradient(to right, #ff8ab3, #f17ab3, #f56eb8);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-code {
 | 
			
		||||
            font-size: 100px;
 | 
			
		||||
            font-size: 120px;
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
            color: #4a89dc;
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
            background: linear-gradient(to right, #ff8ab3, #f17ab3, #f56eb8);
 | 
			
		||||
            -webkit-background-clip: text;
 | 
			
		||||
            -webkit-text-fill-color: transparent;
 | 
			
		||||
            margin: 0 0 20px 0;
 | 
			
		||||
            line-height: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-title {
 | 
			
		||||
            font-size: 28px;
 | 
			
		||||
            color: #ff8ab3;
 | 
			
		||||
            margin-bottom: 15px;
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-message {
 | 
			
		||||
            font-size: 24px;
 | 
			
		||||
            color: #333;
 | 
			
		||||
            font-size: 18px;
 | 
			
		||||
            color: #7a7a7a;
 | 
			
		||||
            margin-bottom: 30px;
 | 
			
		||||
            line-height: 1.6;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .back-button {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            padding: 10px 20px;
 | 
			
		||||
            background-color: #4a89dc;
 | 
			
		||||
            padding: 12px 30px;
 | 
			
		||||
            background: linear-gradient(to right, #ff8ab3, #f17ab3);
 | 
			
		||||
            color: white;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            border-radius: 50px;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
            letter-spacing: 1px;
 | 
			
		||||
            box-shadow: 0 5px 15px rgba(241, 122, 179, 0.4);
 | 
			
		||||
            transition: all 0.3s ease;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .back-button:hover {
 | 
			
		||||
            background-color: #3b78c4;
 | 
			
		||||
            transform: translateY(-3px);
 | 
			
		||||
            box-shadow: 0 8px 20px rgba(241, 122, 179, 0.6);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .decoration {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            width: 80px;
 | 
			
		||||
            height: 80px;
 | 
			
		||||
            border-radius: 50%;
 | 
			
		||||
            background: rgba(255, 138, 179, 0.1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .decoration-1 {
 | 
			
		||||
            top: -20px;
 | 
			
		||||
            left: -20px;
 | 
			
		||||
            width: 120px;
 | 
			
		||||
            height: 120px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .decoration-2 {
 | 
			
		||||
            bottom: -30px;
 | 
			
		||||
            right: -30px;
 | 
			
		||||
            width: 150px;
 | 
			
		||||
            height: 150px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .decoration-3 {
 | 
			
		||||
            top: 60%;
 | 
			
		||||
            left: -40px;
 | 
			
		||||
            width: 100px;
 | 
			
		||||
            height: 100px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .book-icon {
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
            width: 80px;
 | 
			
		||||
            height: 80px;
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            position: relative;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .book-icon svg {
 | 
			
		||||
            fill: #ff8ab3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @media (max-width: 768px) {
 | 
			
		||||
            .error-code {
 | 
			
		||||
                font-size: 100px;
 | 
			
		||||
            }
 | 
			
		||||
            .error-title {
 | 
			
		||||
                font-size: 24px;
 | 
			
		||||
            }
 | 
			
		||||
            .error-container {
 | 
			
		||||
                margin: 0 20px;
 | 
			
		||||
                padding: 30px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="main-container">
 | 
			
		||||
        <div class="error-container">
 | 
			
		||||
            <div class="error-code">404</div>
 | 
			
		||||
            <div class="error-message">页面未找到</div>
 | 
			
		||||
            <p>抱歉,您访问的页面不存在或已被移除。</p>
 | 
			
		||||
            <p style="margin-bottom: 30px;">请检查URL是否正确,或返回首页。</p>
 | 
			
		||||
            <a href="{{ url_for('index') }}" class="back-button">返回首页</a>
 | 
			
		||||
    <div class="error-container">
 | 
			
		||||
        <div class="decoration decoration-1"></div>
 | 
			
		||||
        <div class="decoration decoration-2"></div>
 | 
			
		||||
        <div class="decoration decoration-3"></div>
 | 
			
		||||
 | 
			
		||||
        <div class="book-icon">
 | 
			
		||||
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
 | 
			
		||||
                <path d="M19 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h13c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 18H6V4h13v16z"/>
 | 
			
		||||
                <path d="M9 5h7v2H9zM9 8h7v2H9zM9 11h7v2H9zM9 14h7v2H9z"/>
 | 
			
		||||
            </svg>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="error-code">404</div>
 | 
			
		||||
        <div class="error-title">噢!页面不见了~</div>
 | 
			
		||||
        <div class="error-message">
 | 
			
		||||
            <p>抱歉,您要找的页面似乎藏起来了,或者从未存在过。</p>
 | 
			
		||||
            <p>请检查您输入的网址是否正确,或者回到首页继续浏览吧!</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <a href="{{ url_for('index') }}" class="back-button">返回首页</a>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
@ -32,10 +32,13 @@
 | 
			
		||||
                <li class="{% if '/announcement' in request.path %}active{% endif %}">
 | 
			
		||||
                    <a href="#"><i class="fas fa-bell"></i> 通知公告</a>
 | 
			
		||||
                </li>
 | 
			
		||||
                {% if current_user.role_id == 1 %}
 | 
			
		||||
                {% if current_user.is_authenticated and current_user.role_id == 1 %}
 | 
			
		||||
                <li class="nav-category">管理功能</li>
 | 
			
		||||
                <li class="{% if '/user/manage' in request.path %}active{% endif %}">
 | 
			
		||||
                    <a href="#"><i class="fas fa-users"></i> 用户管理</a>
 | 
			
		||||
                    <a href="{{ url_for('user.user_list') }}"><i class="fas fa-users"></i> 用户管理</a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="{% if '/user/roles' in request.path %}active{% endif %}">
 | 
			
		||||
                    <a href="{{ url_for('user.role_list') }}"><i class="fas fa-user-tag"></i> 角色管理</a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="{% if '/book/list' in request.path %}active{% endif %}">
 | 
			
		||||
                    <a href="{{ url_for('book.book_list') }}"><i class="fas fa-layer-group"></i> 图书管理</a>
 | 
			
		||||
@ -69,6 +72,7 @@
 | 
			
		||||
                        <i class="fas fa-bell"></i>
 | 
			
		||||
                        <span class="badge">3</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% if current_user.is_authenticated %}
 | 
			
		||||
                    <div class="user-info">
 | 
			
		||||
                        <div class="user-avatar">
 | 
			
		||||
                            {{ current_user.username[0] }}
 | 
			
		||||
@ -78,11 +82,17 @@
 | 
			
		||||
                            <span class="user-role">{{ '管理员' if current_user.role_id == 1 else '普通用户' }}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="dropdown-menu">
 | 
			
		||||
                            <a href="#"><i class="fas fa-user-circle"></i> 个人中心</a>
 | 
			
		||||
                            <a href="{{ url_for('user.user_profile') }}"><i class="fas fa-user-circle"></i> 个人中心</a>
 | 
			
		||||
                            <a href="#"><i class="fas fa-cog"></i> 设置</a>
 | 
			
		||||
                            <a href="{{ url_for('user.logout') }}"><i class="fas fa-sign-out-alt"></i> 退出登录</a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="user-info">
 | 
			
		||||
                        <a href="{{ url_for('user.login') }}" class="login-link">登录</a>
 | 
			
		||||
                        <a href="{{ url_for('user.register') }}" class="register-link">注册</a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </header>
 | 
			
		||||
 | 
			
		||||
@ -102,16 +112,21 @@
 | 
			
		||||
        document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
            // 用户菜单下拉
 | 
			
		||||
            const userInfo = document.querySelector('.user-info');
 | 
			
		||||
            userInfo.addEventListener('click', function(e) {
 | 
			
		||||
                userInfo.classList.toggle('active');
 | 
			
		||||
            });
 | 
			
		||||
            if (userInfo) {
 | 
			
		||||
                userInfo.addEventListener('click', function(e) {
 | 
			
		||||
                    if (!e.target.classList.contains('login-link') &&
 | 
			
		||||
                        !e.target.classList.contains('register-link')) {
 | 
			
		||||
                        userInfo.classList.toggle('active');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            // 点击其他区域关闭下拉菜单
 | 
			
		||||
            document.addEventListener('click', function(e) {
 | 
			
		||||
                if (!userInfo.contains(e.target)) {
 | 
			
		||||
                    userInfo.classList.remove('active');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
                // 点击其他区域关闭下拉菜单
 | 
			
		||||
                document.addEventListener('click', function(e) {
 | 
			
		||||
                    if (!userInfo.contains(e.target)) {
 | 
			
		||||
                        userInfo.classList.remove('active');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,8 @@
 | 
			
		||||
 | 
			
		||||
    <div class="books-grid">
 | 
			
		||||
        {% for book in books %}
 | 
			
		||||
        <div class="book-card">
 | 
			
		||||
        <!-- 为每个book-card添加data-id属性 -->
 | 
			
		||||
        <div class="book-card" data-id="{{ book.id }}">
 | 
			
		||||
            <div class="book-cover">
 | 
			
		||||
                {% if book.cover_url %}
 | 
			
		||||
                <img src="{{ book.cover_url }}" alt="{{ book.title }}">
 | 
			
		||||
@ -179,10 +180,31 @@
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 删除确认模态框 -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
  <div class="modal-dialog" role="document">
 | 
			
		||||
    <div class="modal-content">
 | 
			
		||||
      <div class="modal-header">
 | 
			
		||||
        <h5 class="modal-title" id="deleteModalLabel">确认删除</h5>
 | 
			
		||||
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
          <span aria-hidden="true">×</span>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="modal-body">
 | 
			
		||||
        确定要删除《<span id="deleteBookTitle"></span>》吗?此操作不可恢复。
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="modal-footer">
 | 
			
		||||
        <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
			
		||||
        <button type="button" class="btn btn-danger" id="confirmDelete">确认删除</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/book-list.js') }}"></script>
 | 
			
		||||
{{ super() }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										148
									
								
								app/templates/user/edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								app/templates/user/edit.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,148 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}编辑用户 - 图书管理系统{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/user-edit.css') }}">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="user-edit-container">
 | 
			
		||||
    <div class="page-header">
 | 
			
		||||
        <h1>编辑用户</h1>
 | 
			
		||||
        <div class="actions">
 | 
			
		||||
            <a href="{{ url_for('user.user_list') }}" class="btn btn-outline-secondary">
 | 
			
		||||
                <i class="fas fa-arrow-left"></i> 返回用户列表
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="card">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
            {% with messages = get_flashed_messages(with_categories=true) %}
 | 
			
		||||
              {% if messages %}
 | 
			
		||||
                {% for category, message in messages %}
 | 
			
		||||
                  <div class="alert alert-{{ category }}">{{ message }}</div>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
              {% endif %}
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
 | 
			
		||||
            <form method="POST" action="{{ url_for('user.user_edit', user_id=user.id) }}" id="userEditForm">
 | 
			
		||||
                <div class="form-row">
 | 
			
		||||
                    <!-- 用户基本信息 -->
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="username">用户名</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="username" value="{{ user.username }}" readonly>
 | 
			
		||||
                            <small class="form-text text-muted">用户名不可修改</small>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="email">邮箱地址</label>
 | 
			
		||||
                            <input type="email" class="form-control" id="email" name="email" value="{{ user.email or '' }}">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="phone">手机号码</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="phone" name="phone" value="{{ user.phone or '' }}">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="nickname">昵称</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="nickname" name="nickname" value="{{ user.nickname or '' }}">
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- 用户权限和密码 -->
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="role_id">用户角色</label>
 | 
			
		||||
                            <select class="form-control" id="role_id" name="role_id">
 | 
			
		||||
                                {% for role in roles %}
 | 
			
		||||
                                <option value="{{ role.id }}" {% if role.id == user.role_id %}selected{% endif %}>
 | 
			
		||||
                                    {{ role.role_name }}
 | 
			
		||||
                                </option>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="status">用户状态</label>
 | 
			
		||||
                            <select class="form-control" id="status" name="status">
 | 
			
		||||
                                <option value="1" {% if user.status == 1 %}selected{% endif %}>正常</option>
 | 
			
		||||
                                <option value="0" {% if user.status == 0 %}selected{% endif %}>禁用</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="password">重置密码</label>
 | 
			
		||||
                            <input type="password" class="form-control" id="password" name="password">
 | 
			
		||||
                            <small class="form-text text-muted">留空表示不修改密码</small>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group" id="confirmPasswordGroup" style="display: none;">
 | 
			
		||||
                            <label for="confirm_password">确认密码</label>
 | 
			
		||||
                            <input type="password" class="form-control" id="confirm_password" name="confirm_password">
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- 附加信息 -->
 | 
			
		||||
                <div class="form-row">
 | 
			
		||||
                    <div class="col-md-12">
 | 
			
		||||
                        <div class="user-info-box">
 | 
			
		||||
                            <div class="info-item">
 | 
			
		||||
                                <span class="info-label">用户ID:</span>
 | 
			
		||||
                                <span class="info-value">{{ user.id }}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="info-item">
 | 
			
		||||
                                <span class="info-label">注册时间:</span>
 | 
			
		||||
                                <span class="info-value">{{ user.created_at }}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="info-item">
 | 
			
		||||
                                <span class="info-label">最后更新:</span>
 | 
			
		||||
                                <span class="info-value">{{ user.updated_at }}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- 提交按钮区域 -->
 | 
			
		||||
                <div class="form-actions">
 | 
			
		||||
                    <button type="submit" class="btn btn-primary">
 | 
			
		||||
                        <i class="fas fa-save"></i> 保存修改
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <a href="{{ url_for('user.user_list') }}" class="btn btn-secondary">
 | 
			
		||||
                        取消
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 操作成功提示模态框 -->
 | 
			
		||||
<div class="modal fade" id="successModal" tabindex="-1" role="dialog" aria-labelledby="successModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog" role="document">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="successModalLabel">操作成功</h5>
 | 
			
		||||
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                用户信息已成功更新。
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
 | 
			
		||||
                <a href="{{ url_for('user.user_list') }}" class="btn btn-primary">返回用户列表</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/user-edit.js') }}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										190
									
								
								app/templates/user/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								app/templates/user/list.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}用户管理 - 图书管理系统{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/user-list.css') }}">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="user-list-container">
 | 
			
		||||
    <!-- 页面标题 -->
 | 
			
		||||
    <div class="page-header">
 | 
			
		||||
        <h1>用户管理</h1>
 | 
			
		||||
        <div class="actions">
 | 
			
		||||
            <a href="{{ url_for('user.register') }}" class="btn btn-primary">
 | 
			
		||||
                <i class="fas fa-user-plus"></i> 添加用户
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 搜索和过滤区域 -->
 | 
			
		||||
    <div class="search-filter-container">
 | 
			
		||||
        <form method="GET" action="{{ url_for('user.user_list') }}" class="search-filter-form">
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <div class="search-box">
 | 
			
		||||
                    <input type="text" name="search" value="{{ search }}" placeholder="搜索用户名/邮箱/昵称/手机" class="form-control">
 | 
			
		||||
                    <button type="submit" class="btn btn-search">
 | 
			
		||||
                        <i class="fas fa-search"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="filter-box">
 | 
			
		||||
                    <select name="status" class="form-control">
 | 
			
		||||
                        <option value="">所有状态</option>
 | 
			
		||||
                        <option value="1" {% if status == 1 %}selected{% endif %}>正常</option>
 | 
			
		||||
                        <option value="0" {% if status == 0 %}selected{% endif %}>禁用</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
 | 
			
		||||
                    <select name="role_id" class="form-control">
 | 
			
		||||
                        <option value="">所有角色</option>
 | 
			
		||||
                        {% for role in roles %}
 | 
			
		||||
                        <option value="{{ role.id }}" {% if role_id == role.id %}selected{% endif %}>{{ role.role_name }}</option>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </select>
 | 
			
		||||
 | 
			
		||||
                    <button type="submit" class="btn btn-filter">筛选</button>
 | 
			
		||||
                    <a href="{{ url_for('user.user_list') }}" class="btn btn-reset">重置</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 用户列表表格 -->
 | 
			
		||||
    <div class="table-responsive">
 | 
			
		||||
        <table class="table">
 | 
			
		||||
            <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>ID</th>
 | 
			
		||||
                    <th>用户名</th>
 | 
			
		||||
                    <th>昵称</th>
 | 
			
		||||
                    <th>邮箱</th>
 | 
			
		||||
                    <th>手机号</th>
 | 
			
		||||
                    <th>角色</th>
 | 
			
		||||
                    <th>状态</th>
 | 
			
		||||
                    <th>注册时间</th>
 | 
			
		||||
                    <th>操作</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                {% for user in pagination.items %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>{{ user.id }}</td>
 | 
			
		||||
                    <td>{{ user.username }}</td>
 | 
			
		||||
                    <td>{{ user.nickname or '-' }}</td>
 | 
			
		||||
                    <td>{{ user.email or '-' }}</td>
 | 
			
		||||
                    <td>{{ user.phone or '-' }}</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {% for role in roles %}
 | 
			
		||||
                            {% if role.id == user.role_id %}
 | 
			
		||||
                                {{ role.role_name }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <span class="status-badge {% if user.status == 1 %}active{% else %}inactive{% endif %}">
 | 
			
		||||
                            {{ '正常' if user.status == 1 else '禁用' }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>{{ user.created_at }}</td>
 | 
			
		||||
                    <td class="actions">
 | 
			
		||||
                        <a href="{{ url_for('user.user_edit', user_id=user.id) }}" class="btn btn-sm btn-info" title="编辑">
 | 
			
		||||
                            <i class="fas fa-edit"></i>
 | 
			
		||||
                        </a>
 | 
			
		||||
 | 
			
		||||
                        {% if user.id != session.get('user_id') %}
 | 
			
		||||
                            {% if user.status == 1 %}
 | 
			
		||||
                            <button class="btn btn-sm btn-warning toggle-status" data-id="{{ user.id }}" data-status="0" title="禁用">
 | 
			
		||||
                                <i class="fas fa-ban"></i>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                            <button class="btn btn-sm btn-success toggle-status" data-id="{{ user.id }}" data-status="1" title="启用">
 | 
			
		||||
                                <i class="fas fa-check"></i>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
 | 
			
		||||
                            <button class="btn btn-sm btn-danger delete-user" data-id="{{ user.id }}" title="删除">
 | 
			
		||||
                                <i class="fas fa-trash"></i>
 | 
			
		||||
                            </button>
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                            <span class="text-muted">(当前用户)</span>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                {% else %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td colspan="9" class="text-center">暂无用户数据</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 分页控件 -->
 | 
			
		||||
    {% if pagination.pages > 1 %}
 | 
			
		||||
    <div class="pagination-container">
 | 
			
		||||
        <ul class="pagination">
 | 
			
		||||
            {% if pagination.has_prev %}
 | 
			
		||||
            <li class="page-item">
 | 
			
		||||
                <a class="page-link" href="{{ url_for('user.user_list', page=pagination.prev_num, search=search, status=status, role_id=role_id) }}">
 | 
			
		||||
                    <i class="fas fa-chevron-left"></i>
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 | 
			
		||||
            {% for page in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
 | 
			
		||||
                {% if page %}
 | 
			
		||||
                    {% if page == pagination.page %}
 | 
			
		||||
                    <li class="page-item active">
 | 
			
		||||
                        <span class="page-link">{{ page }}</span>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <li class="page-item">
 | 
			
		||||
                        <a class="page-link" href="{{ url_for('user.user_list', page=page, search=search, status=status, role_id=role_id) }}">{{ page }}</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                {% else %}
 | 
			
		||||
                    <li class="page-item disabled">
 | 
			
		||||
                        <span class="page-link">...</span>
 | 
			
		||||
                    </li>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
 | 
			
		||||
            {% if pagination.has_next %}
 | 
			
		||||
            <li class="page-item">
 | 
			
		||||
                <a class="page-link" href="{{ url_for('user.user_list', page=pagination.next_num, search=search, status=status, role_id=role_id) }}">
 | 
			
		||||
                    <i class="fas fa-chevron-right"></i>
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 确认删除模态框 -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog" role="document">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="deleteModalLabel">确认删除</h5>
 | 
			
		||||
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                您确定要删除这个用户吗?此操作不可逆。
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
			
		||||
                <button type="button" class="btn btn-danger" id="confirmDelete">确认删除</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/user-list.js') }}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										188
									
								
								app/templates/user/profile.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								app/templates/user/profile.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,188 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}个人中心 - 图书管理系统{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/user-profile.css') }}">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="profile-container">
 | 
			
		||||
    <div class="page-header">
 | 
			
		||||
        <h1>个人中心</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="profile-content">
 | 
			
		||||
        <!-- 左侧用户信息展示 -->
 | 
			
		||||
        <div class="profile-sidebar">
 | 
			
		||||
            <div class="user-avatar-container">
 | 
			
		||||
                <div class="user-avatar large">
 | 
			
		||||
                    {{ user.username[0] }}
 | 
			
		||||
                </div>
 | 
			
		||||
                <h3 class="user-name">{{ user.nickname or user.username }}</h3>
 | 
			
		||||
                <p class="user-role">{{ '管理员' if user.role_id == 1 else '普通用户' }}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="user-stats">
 | 
			
		||||
                <div class="stat-item">
 | 
			
		||||
                    <div class="stat-value" id="borrowCount">--</div>
 | 
			
		||||
                    <div class="stat-label">借阅中</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="stat-item">
 | 
			
		||||
                    <div class="stat-value" id="returnedCount">--</div>
 | 
			
		||||
                    <div class="stat-label">已归还</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="stat-item">
 | 
			
		||||
                    <div class="stat-value" id="overdueCount">--</div>
 | 
			
		||||
                    <div class="stat-label">已逾期</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="account-info">
 | 
			
		||||
                <div class="info-row">
 | 
			
		||||
                    <span class="info-label">用户名</span>
 | 
			
		||||
                    <span class="info-value">{{ user.username }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="info-row">
 | 
			
		||||
                    <span class="info-label">用户ID</span>
 | 
			
		||||
                    <span class="info-value">{{ user.id }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="info-row">
 | 
			
		||||
                    <span class="info-label">注册时间</span>
 | 
			
		||||
                    <span class="info-value">{{ user.created_at }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="info-row">
 | 
			
		||||
                    <span class="info-label">最后更新</span>
 | 
			
		||||
                    <span class="info-value">{{ user.updated_at }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- 右侧内容区域:包含编辑选项卡 -->
 | 
			
		||||
        <div class="profile-main">
 | 
			
		||||
            <!-- 提示消息 -->
 | 
			
		||||
            {% with messages = get_flashed_messages(with_categories=true) %}
 | 
			
		||||
              {% if messages %}
 | 
			
		||||
                {% for category, message in messages %}
 | 
			
		||||
                  <div class="alert alert-{{ category }}">{{ message }}</div>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
              {% endif %}
 | 
			
		||||
            {% endwith %}
 | 
			
		||||
 | 
			
		||||
            <!-- 选项卡导航 -->
 | 
			
		||||
            <ul class="nav nav-tabs" id="profileTabs" role="tablist">
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link active" id="basic-tab" data-toggle="tab" href="#basic" role="tab" aria-controls="basic" aria-selected="true">
 | 
			
		||||
                        <i class="fas fa-user"></i> 基本信息
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link" id="security-tab" data-toggle="tab" href="#security" role="tab" aria-controls="security" aria-selected="false">
 | 
			
		||||
                        <i class="fas fa-lock"></i> 安全设置
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link" id="activity-tab" data-toggle="tab" href="#activity" role="tab" aria-controls="activity" aria-selected="false">
 | 
			
		||||
                        <i class="fas fa-history"></i> 最近活动
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
 | 
			
		||||
            <!-- 选项卡内容 -->
 | 
			
		||||
            <div class="tab-content" id="profileTabsContent">
 | 
			
		||||
                <!-- 基本信息选项卡 -->
 | 
			
		||||
                <div class="tab-pane fade show active" id="basic" role="tabpanel" aria-labelledby="basic-tab">
 | 
			
		||||
                    <form method="POST" action="{{ url_for('user.user_profile') }}" id="profileForm">
 | 
			
		||||
                        <div class="form-section">
 | 
			
		||||
                            <h4>个人信息</h4>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="nickname">昵称</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="nickname" name="nickname" value="{{ user.nickname or '' }}" placeholder="请输入您的昵称">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="email">邮箱地址</label>
 | 
			
		||||
                                <input type="email" class="form-control" id="email" name="email" value="{{ user.email or '' }}" placeholder="请输入您的邮箱">
 | 
			
		||||
                                <small class="form-text text-muted">用于接收系统通知和找回密码</small>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="phone">手机号码</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="phone" name="phone" value="{{ user.phone or '' }}" placeholder="请输入您的手机号">
 | 
			
		||||
                                <small class="form-text text-muted">用于接收借阅提醒和系统通知</small>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-actions">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary" name="form_type" value="profile">
 | 
			
		||||
                                <i class="fas fa-save"></i> 保存修改
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- 安全设置选项卡 -->
 | 
			
		||||
                <div class="tab-pane fade" id="security" role="tabpanel" aria-labelledby="security-tab">
 | 
			
		||||
                    <form method="POST" action="{{ url_for('user.user_profile') }}" id="passwordForm">
 | 
			
		||||
                        <div class="form-section">
 | 
			
		||||
                            <h4>修改密码</h4>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="current_password">当前密码</label>
 | 
			
		||||
                                <input type="password" class="form-control" id="current_password" name="current_password" placeholder="请输入当前密码">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="new_password">新密码</label>
 | 
			
		||||
                                <input type="password" class="form-control" id="new_password" name="new_password" placeholder="请输入新密码">
 | 
			
		||||
                                <small class="form-text text-muted">密码长度至少为6个字符</small>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="confirm_password">确认新密码</label>
 | 
			
		||||
                                <input type="password" class="form-control" id="confirm_password" name="confirm_password" placeholder="请再次输入新密码">
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-actions">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary" name="form_type" value="password">
 | 
			
		||||
                                <i class="fas fa-key"></i> 更新密码
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- 最近活动选项卡 -->
 | 
			
		||||
                <div class="tab-pane fade" id="activity" role="tabpanel" aria-labelledby="activity-tab">
 | 
			
		||||
                    <div class="activity-header">
 | 
			
		||||
                        <h4>最近活动</h4>
 | 
			
		||||
                        <div class="activity-filter">
 | 
			
		||||
                            <label for="activityFilter">显示:</label>
 | 
			
		||||
                            <select id="activityFilter" class="form-control form-control-sm">
 | 
			
		||||
                                <option value="all">所有活动</option>
 | 
			
		||||
                                <option value="login">登录记录</option>
 | 
			
		||||
                                <option value="borrow">借阅活动</option>
 | 
			
		||||
                                <option value="return">归还活动</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div class="activity-timeline" id="activityTimeline">
 | 
			
		||||
                        <div class="timeline-loading">
 | 
			
		||||
                            <div class="spinner-border text-primary" role="status">
 | 
			
		||||
                                <span class="sr-only">Loading...</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <p>加载中...</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/user-profile.js') }}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										194
									
								
								app/templates/user/roles.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								app/templates/user/roles.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,194 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}角色管理 - 图书管理系统{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block head %}
 | 
			
		||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/user-roles.css') }}">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="roles-container">
 | 
			
		||||
    <!-- 页面标题 -->
 | 
			
		||||
    <div class="page-header">
 | 
			
		||||
        <h1>角色管理</h1>
 | 
			
		||||
        <div class="actions">
 | 
			
		||||
            <button class="btn btn-primary" id="addRoleBtn">
 | 
			
		||||
                <i class="fas fa-plus"></i> 添加角色
 | 
			
		||||
            </button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 角色列表卡片 -->
 | 
			
		||||
    <div class="role-list">
 | 
			
		||||
        {% for role in roles %}
 | 
			
		||||
        <div class="role-card" data-id="{{ role.id }}">
 | 
			
		||||
            <div class="role-header">
 | 
			
		||||
                <h3 class="role-name">{{ role.role_name }}</h3>
 | 
			
		||||
                <div class="role-actions">
 | 
			
		||||
                    <button class="btn btn-sm btn-edit-role" title="编辑角色">
 | 
			
		||||
                        <i class="fas fa-edit"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% if role.id not in [1, 2] %}
 | 
			
		||||
                    <button class="btn btn-sm btn-delete-role" title="删除角色">
 | 
			
		||||
                        <i class="fas fa-trash"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="role-description">
 | 
			
		||||
                {% if role.description %}
 | 
			
		||||
                {{ role.description }}
 | 
			
		||||
                {% else %}
 | 
			
		||||
                <span class="text-muted">暂无描述</span>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="role-stats">
 | 
			
		||||
                <div class="stat-item">
 | 
			
		||||
                    <i class="fas fa-users"></i> <span id="userCount-{{ role.id }}">--</span> 用户
 | 
			
		||||
                </div>
 | 
			
		||||
                {% if role.id == 1 %}
 | 
			
		||||
                <div class="role-badge admin">管理员</div>
 | 
			
		||||
                {% elif role.id == 2 %}
 | 
			
		||||
                <div class="role-badge user">普通用户</div>
 | 
			
		||||
                {% else %}
 | 
			
		||||
                <div class="role-badge custom">自定义</div>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% else %}
 | 
			
		||||
        <div class="no-data-message">
 | 
			
		||||
            <i class="fas fa-users-slash"></i>
 | 
			
		||||
            <p>暂无角色数据</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 权限描述 -->
 | 
			
		||||
    <div class="permissions-info">
 | 
			
		||||
        <h3>角色权限说明</h3>
 | 
			
		||||
        <div class="card">
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
                <table class="table permission-table">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th>功能模块</th>
 | 
			
		||||
                            <th>管理员</th>
 | 
			
		||||
                            <th>普通用户</th>
 | 
			
		||||
                            <th>自定义角色</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>图书浏览</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>借阅图书</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>图书管理</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>用户管理</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>借阅管理</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>库存管理</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>统计分析</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>系统设置</td>
 | 
			
		||||
                            <td><i class="fas fa-check text-success"></i></td>
 | 
			
		||||
                            <td><i class="fas fa-times text-danger"></i></td>
 | 
			
		||||
                            <td>可配置</td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 角色编辑模态框 -->
 | 
			
		||||
<div class="modal fade" id="roleModal" tabindex="-1" role="dialog" aria-labelledby="roleModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog" role="document">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="roleModalLabel">添加角色</h5>
 | 
			
		||||
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <form id="roleForm">
 | 
			
		||||
                    <input type="hidden" id="roleId" value="">
 | 
			
		||||
 | 
			
		||||
                    <div class="form-group">
 | 
			
		||||
                        <label for="roleName">角色名称</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="roleName" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div class="form-group">
 | 
			
		||||
                        <label for="roleDescription">角色描述</label>
 | 
			
		||||
                        <textarea class="form-control" id="roleDescription" rows="3"></textarea>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
			
		||||
                <button type="button" class="btn btn-primary" id="saveRoleBtn">保存</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 确认删除模态框 -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog" role="document">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="deleteModalLabel">确认删除</h5>
 | 
			
		||||
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                您确定要删除这个角色吗?此操作不可逆。
 | 
			
		||||
                <p class="text-danger mt-3">注意:删除角色将会影响所有使用此角色的用户。</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
 | 
			
		||||
                <button type="button" class="btn btn-danger" id="confirmDeleteBtn">确认删除</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
<script src="{{ url_for('static', filename='js/user-roles.js') }}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										6205
									
								
								code_collection.txt
									
									
									
									
									
								
							
							
						
						
									
										6205
									
								
								code_collection.txt
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -9,4 +9,5 @@ xlrd==2.0.1
 | 
			
		||||
email-validator==2.0.0
 | 
			
		||||
pillow==9.5.0
 | 
			
		||||
numpy
 | 
			
		||||
pandas
 | 
			
		||||
pandas
 | 
			
		||||
flask-login
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user