完成权限管理分类
This commit is contained in:
parent
73a08d0ab2
commit
89d17f1ba6
@ -1,6 +1,7 @@
|
||||
from flask import Flask, render_template, session, g, Markup, redirect, url_for
|
||||
from flask_login import LoginManager
|
||||
from app.models.user import db, User
|
||||
from app.models.database import db
|
||||
from app.models.user import User
|
||||
from app.controllers.user import user_bp
|
||||
from app.controllers.book import book_bp
|
||||
from app.controllers.borrow import borrow_bp
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
|
||||
from app.models.announcement import Announcement
|
||||
from app.models.log import Log
|
||||
from app.utils.auth import admin_required
|
||||
from app.utils.auth import permission_required # 修改导入
|
||||
from flask_login import login_required, current_user
|
||||
from datetime import datetime
|
||||
from app.models.notification import Notification
|
||||
from app import db # 为mark_all_as_read函数添加db导入
|
||||
|
||||
# 创建蓝图
|
||||
announcement_bp = Blueprint('announcement', __name__)
|
||||
@ -41,7 +42,7 @@ def announcement_detail(announcement_id):
|
||||
|
||||
@announcement_bp.route('/manage', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_announcements') # 替代 @admin_required
|
||||
def manage_announcements():
|
||||
"""管理员公告管理页面"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -86,7 +87,7 @@ def manage_announcements():
|
||||
|
||||
@announcement_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_announcements') # 替代 @admin_required
|
||||
def add_announcement():
|
||||
"""添加公告"""
|
||||
if request.method == 'POST':
|
||||
@ -127,7 +128,7 @@ def add_announcement():
|
||||
|
||||
@announcement_bp.route('/edit/<int:announcement_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_announcements') # 替代 @admin_required
|
||||
def edit_announcement(announcement_id):
|
||||
"""编辑公告"""
|
||||
announcement = Announcement.get_announcement_by_id(announcement_id)
|
||||
@ -174,7 +175,7 @@ def edit_announcement(announcement_id):
|
||||
|
||||
@announcement_bp.route('/status/<int:announcement_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_announcements') # 替代 @admin_required
|
||||
def change_status(announcement_id):
|
||||
"""更改公告状态"""
|
||||
data = request.get_json()
|
||||
@ -209,7 +210,7 @@ def change_status(announcement_id):
|
||||
|
||||
@announcement_bp.route('/top/<int:announcement_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_announcements') # 替代 @admin_required
|
||||
def change_top_status(announcement_id):
|
||||
"""更改公告置顶状态"""
|
||||
data = request.get_json()
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify
|
||||
from app.models.book import Book, Category
|
||||
from app.models.user import db
|
||||
from app.utils.auth import login_required, admin_required
|
||||
from flask_login import current_user # 移除重复的 login_required 导入
|
||||
from app.utils.auth import login_required, permission_required # 修改导入,替换admin_required为permission_required
|
||||
from flask_login import current_user
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
import datetime
|
||||
import pandas as pd
|
||||
import uuid
|
||||
from app.models.log import Log # 导入日志模型
|
||||
from app.models.log import Log
|
||||
|
||||
book_bp = Blueprint('book', __name__)
|
||||
|
||||
|
||||
@book_bp.route('/admin/list')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_books') # 替换 @admin_required
|
||||
def admin_book_list():
|
||||
print(f"DEBUG: admin_book_list 函数被调用,用户={current_user.username},认证状态={current_user.is_authenticated}")
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -62,11 +62,11 @@ def admin_book_list():
|
||||
category_id=category_id,
|
||||
sort=sort,
|
||||
order=order,
|
||||
current_user=current_user, # 使用current_user替代g.user
|
||||
is_admin_view=True) # 指明这是管理视图
|
||||
current_user=current_user,
|
||||
is_admin_view=True)
|
||||
|
||||
|
||||
# 图书列表页面
|
||||
# 图书列表页面 - 不需要修改,已经只有@login_required
|
||||
@book_bp.route('/list')
|
||||
@login_required
|
||||
def book_list():
|
||||
@ -122,10 +122,10 @@ def book_list():
|
||||
category_id=category_id,
|
||||
sort=sort,
|
||||
order=order,
|
||||
current_user=current_user) # 使用current_user替代g.user
|
||||
current_user=current_user)
|
||||
|
||||
|
||||
# 图书详情页面
|
||||
# 图书详情页面 - 不需要修改,已经只有@login_required
|
||||
@book_bp.route('/detail/<int:book_id>')
|
||||
@login_required
|
||||
def book_detail(book_id):
|
||||
@ -164,7 +164,7 @@ def book_detail(book_id):
|
||||
# 添加图书页面
|
||||
@book_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_books') # 替换 @admin_required
|
||||
def add_book():
|
||||
if request.method == 'POST':
|
||||
title = request.form.get('title')
|
||||
@ -269,7 +269,7 @@ def add_book():
|
||||
change_type='入库',
|
||||
change_amount=stock,
|
||||
after_stock=stock,
|
||||
operator_id=current_user.id, # 使用current_user.id替代g.user.id
|
||||
operator_id=current_user.id,
|
||||
remark='新书入库',
|
||||
changed_at=datetime.datetime.now()
|
||||
)
|
||||
@ -329,7 +329,7 @@ def add_book():
|
||||
# 编辑图书
|
||||
@book_bp.route('/edit/<int:book_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_books') # 替换 @admin_required
|
||||
def edit_book(book_id):
|
||||
book = Book.query.get_or_404(book_id)
|
||||
|
||||
@ -422,7 +422,7 @@ def edit_book(book_id):
|
||||
change_type=change_type,
|
||||
change_amount=abs(change_amount),
|
||||
after_stock=new_stock,
|
||||
operator_id=current_user.id, # 使用current_user.id替代g.user.id
|
||||
operator_id=current_user.id,
|
||||
remark=f'管理员编辑图书库存 - {book.title}',
|
||||
changed_at=datetime.datetime.now()
|
||||
)
|
||||
@ -500,7 +500,7 @@ def edit_book(book_id):
|
||||
# 删除图书
|
||||
@book_bp.route('/delete/<int:book_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_books') # 替换 @admin_required
|
||||
def delete_book(book_id):
|
||||
book = Book.query.get_or_404(book_id)
|
||||
|
||||
@ -541,7 +541,7 @@ def delete_book(book_id):
|
||||
# 图书分类管理
|
||||
@book_bp.route('/categories', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_categories') # 替换 @admin_required
|
||||
def category_list():
|
||||
categories = Category.query.all()
|
||||
|
||||
@ -559,7 +559,7 @@ def category_list():
|
||||
# 添加分类
|
||||
@book_bp.route('/categories/add', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_categories') # 替换 @admin_required
|
||||
def add_category():
|
||||
name = request.form.get('name')
|
||||
parent_id = request.form.get('parent_id') or None
|
||||
@ -586,7 +586,7 @@ def add_category():
|
||||
# 编辑分类
|
||||
@book_bp.route('/categories/edit/<int:category_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_categories') # 替换 @admin_required
|
||||
def edit_category(category_id):
|
||||
category = Category.query.get_or_404(category_id)
|
||||
|
||||
@ -622,7 +622,7 @@ def edit_category(category_id):
|
||||
# 删除分类
|
||||
@book_bp.route('/categories/delete/<int:category_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_categories') # 替换 @admin_required
|
||||
def delete_category(category_id):
|
||||
category = Category.query.get_or_404(category_id)
|
||||
|
||||
@ -675,7 +675,7 @@ def delete_category(category_id):
|
||||
# 批量导入图书
|
||||
@book_bp.route('/import', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('import_export_books') # 替换 @admin_required
|
||||
def import_books():
|
||||
if request.method == 'POST':
|
||||
if 'file' not in request.files:
|
||||
@ -741,7 +741,7 @@ def import_books():
|
||||
change_type='入库',
|
||||
change_amount=book.stock,
|
||||
after_stock=book.stock,
|
||||
operator_id=current_user.id, # 使用current_user.id
|
||||
operator_id=current_user.id,
|
||||
remark='批量导入图书',
|
||||
changed_at=datetime.datetime.now()
|
||||
)
|
||||
@ -784,13 +784,13 @@ def import_books():
|
||||
flash('只支持Excel文件(.xlsx, .xls)', 'danger')
|
||||
return redirect(request.url)
|
||||
|
||||
return render_template('book/import.html', current_user=current_user) # 使用current_user
|
||||
return render_template('book/import.html', current_user=current_user)
|
||||
|
||||
|
||||
# 导出图书
|
||||
@book_bp.route('/export')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('import_export_books') # 替换 @admin_required
|
||||
def export_books():
|
||||
# 获取查询参数
|
||||
search = request.args.get('search', '')
|
||||
@ -871,8 +871,7 @@ def test_permissions():
|
||||
"""
|
||||
|
||||
|
||||
# 添加到app/controllers/book.py文件中
|
||||
|
||||
# 图书浏览页面 - 不需要修改,已经只有@login_required
|
||||
@book_bp.route('/browse')
|
||||
@login_required
|
||||
def browse_books():
|
||||
@ -927,12 +926,12 @@ def browse_books():
|
||||
categories=categories,
|
||||
category_id=category_id,
|
||||
sort=sort,
|
||||
order=order) # current_user自动传递到模板
|
||||
order=order)
|
||||
|
||||
|
||||
@book_bp.route('/template/download')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('import_export_books') # 替换 @admin_required
|
||||
def download_template():
|
||||
"""生成并下载Excel图书导入模板"""
|
||||
# 创建一个简单的DataFrame作为模板
|
||||
|
||||
@ -6,7 +6,7 @@ from app.models.inventory import InventoryLog
|
||||
from app.models.user import db, User
|
||||
from app.models.log import Log # 导入日志模型
|
||||
import datetime
|
||||
from app.utils.auth import admin_required
|
||||
from app.utils.auth import permission_required # 修改导入,使用permission_required而不是admin_required
|
||||
|
||||
# 创建借阅蓝图
|
||||
borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow')
|
||||
@ -191,7 +191,7 @@ def return_book(borrow_id):
|
||||
borrow_record = BorrowRecord.query.get_or_404(borrow_id)
|
||||
|
||||
# 检查是否是自己的借阅记录或者是管理员
|
||||
if borrow_record.user_id != current_user.id and current_user.role_id != 1:
|
||||
if borrow_record.user_id != current_user.id and not current_user.has_permission('manage_borrows'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '您无权执行此操作'
|
||||
@ -268,7 +268,7 @@ def renew_book(borrow_id):
|
||||
borrow_record = BorrowRecord.query.get_or_404(borrow_id)
|
||||
|
||||
# 检查是否是自己的借阅记录或者是管理员
|
||||
if borrow_record.user_id != current_user.id and current_user.role_id != 1:
|
||||
if borrow_record.user_id != current_user.id and not current_user.has_permission('manage_borrows'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '您无权执行此操作'
|
||||
@ -376,7 +376,7 @@ def my_borrows():
|
||||
|
||||
@borrow_bp.route('/manage')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_borrows') # 替代 @admin_required
|
||||
def manage_borrows():
|
||||
"""管理员查看所有借阅记录"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -444,7 +444,7 @@ def manage_borrows():
|
||||
|
||||
@borrow_bp.route('/admin/add', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_borrows') # 替代 @admin_required
|
||||
def admin_add_borrow():
|
||||
"""管理员为用户添加借阅记录"""
|
||||
user_id = request.form.get('user_id', type=int)
|
||||
@ -532,7 +532,7 @@ def admin_add_borrow():
|
||||
|
||||
@borrow_bp.route('/overdue')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_overdue') # 替代 @admin_required
|
||||
def overdue_borrows():
|
||||
"""查看逾期借阅"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -566,7 +566,7 @@ def overdue_borrows():
|
||||
|
||||
@borrow_bp.route('/overdue/notify/<int:borrow_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_overdue') # 替代 @admin_required
|
||||
def notify_overdue(borrow_id):
|
||||
"""发送逾期通知"""
|
||||
from app.models.notification import Notification
|
||||
|
||||
@ -5,7 +5,7 @@ from app.models.book import Book
|
||||
from app.models.inventory import InventoryLog
|
||||
from app.models.log import Log # 导入日志模型
|
||||
from app.models.user import db
|
||||
from app.utils.auth import admin_required
|
||||
from app.utils.auth import permission_required # 修改导入,使用permission_required替代admin_required
|
||||
from datetime import datetime
|
||||
|
||||
inventory_bp = Blueprint('inventory', __name__, url_prefix='/inventory')
|
||||
@ -13,9 +13,9 @@ inventory_bp = Blueprint('inventory', __name__, url_prefix='/inventory')
|
||||
|
||||
@inventory_bp.route('/')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_inventory') # 替代 @admin_required
|
||||
def inventory_list():
|
||||
"""库存管理页面 - 只有管理员有权限进入"""
|
||||
"""库存管理页面 - 只有拥有库存管理权限的用户有权限进入"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
@ -47,7 +47,7 @@ def inventory_list():
|
||||
user_id=current_user.id,
|
||||
target_type="inventory",
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员访问库存管理页面,搜索条件:{search if search else '无'}"
|
||||
description=f"用户访问库存管理页面,搜索条件:{search if search else '无'}"
|
||||
)
|
||||
|
||||
return render_template('inventory/list.html',
|
||||
@ -60,7 +60,7 @@ def inventory_list():
|
||||
|
||||
@inventory_bp.route('/adjust/<int:book_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_inventory') # 替代 @admin_required
|
||||
def adjust_inventory(book_id):
|
||||
"""调整图书库存"""
|
||||
book = Book.query.get_or_404(book_id)
|
||||
@ -73,7 +73,7 @@ def adjust_inventory(book_id):
|
||||
target_type="book",
|
||||
target_id=book.id,
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员查看图书《{book.title}》的库存调整页面"
|
||||
description=f"用户查看图书《{book.title}》的库存调整页面"
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
@ -123,7 +123,7 @@ def adjust_inventory(book_id):
|
||||
target_type="book",
|
||||
target_id=book.id,
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员对图书《{book.title}》进行{operation_desc}操作,数量:{change_amount},"
|
||||
description=f"用户对图书《{book.title}》进行{operation_desc}操作,数量:{change_amount},"
|
||||
f"原库存:{original_stock},现库存:{after_stock},备注:{remark}"
|
||||
)
|
||||
|
||||
@ -140,7 +140,7 @@ def adjust_inventory(book_id):
|
||||
|
||||
@inventory_bp.route('/logs')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_inventory') # 替代 @admin_required
|
||||
def inventory_logs():
|
||||
"""查看库存变动日志"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -189,7 +189,7 @@ def inventory_logs():
|
||||
user_id=current_user.id,
|
||||
target_type="inventory_log",
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员查看库存变动日志,筛选条件:{', '.join(filter_desc) if filter_desc else '无'}"
|
||||
description=f"用户查看库存变动日志,筛选条件:{', '.join(filter_desc) if filter_desc else '无'}"
|
||||
)
|
||||
|
||||
return render_template('inventory/logs.html',
|
||||
@ -205,7 +205,7 @@ def inventory_logs():
|
||||
|
||||
@inventory_bp.route('/book/<int:book_id>/logs')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('manage_inventory') # 替代 @admin_required
|
||||
def book_inventory_logs(book_id):
|
||||
"""查看特定图书的库存变动日志"""
|
||||
book = Book.query.get_or_404(book_id)
|
||||
@ -223,7 +223,7 @@ def book_inventory_logs(book_id):
|
||||
target_type="book",
|
||||
target_id=book.id,
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员查看图书《{book.title}》的库存变动日志"
|
||||
description=f"用户查看图书《{book.title}》的库存变动日志"
|
||||
)
|
||||
|
||||
return render_template('inventory/book_logs.html',
|
||||
|
||||
@ -2,7 +2,7 @@ from flask import Blueprint, render_template, request, jsonify
|
||||
from flask_login import current_user, login_required
|
||||
from app.models.log import Log
|
||||
from app.models.user import User, db # 导入db
|
||||
from app.controllers.user import admin_required # 导入admin_required装饰器
|
||||
from app.utils.auth import permission_required # 更改为导入permission_required装饰器
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 创建蓝图
|
||||
@ -11,7 +11,7 @@ log_bp = Blueprint('log', __name__, url_prefix='/log')
|
||||
|
||||
@log_bp.route('/list')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_logs') # 替代 @admin_required
|
||||
def log_list():
|
||||
"""日志列表页面"""
|
||||
# 获取筛选参数
|
||||
@ -81,7 +81,7 @@ def log_list():
|
||||
|
||||
@log_bp.route('/detail/<int:log_id>')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_logs') # 替代 @admin_required
|
||||
def log_detail(log_id):
|
||||
"""日志详情页面"""
|
||||
log = Log.query.get_or_404(log_id)
|
||||
@ -90,7 +90,7 @@ def log_detail(log_id):
|
||||
|
||||
@log_bp.route('/api/export', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_logs') # 替代 @admin_required
|
||||
def export_logs():
|
||||
"""导出日志API"""
|
||||
import csv
|
||||
@ -171,7 +171,7 @@ def export_logs():
|
||||
|
||||
@log_bp.route('/api/clear', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_logs') # 替代 @admin_required
|
||||
def clear_logs():
|
||||
"""清空日志API"""
|
||||
data = request.get_json()
|
||||
|
||||
@ -4,7 +4,7 @@ from flask_login import login_required, current_user
|
||||
from app.models.book import Book, db
|
||||
from app.models.borrow import BorrowRecord
|
||||
from app.models.user import User
|
||||
from app.utils.auth import admin_required
|
||||
from app.utils.auth import permission_required # 修改为导入permission_required
|
||||
from app.models.log import Log # 导入日志模型
|
||||
from sqlalchemy import func, case, desc, and_
|
||||
from datetime import datetime, timedelta
|
||||
@ -15,7 +15,7 @@ statistics_bp = Blueprint('statistics', __name__, url_prefix='/statistics')
|
||||
|
||||
@statistics_bp.route('/')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def index():
|
||||
"""统计分析首页"""
|
||||
# 记录访问统计分析首页的日志
|
||||
@ -30,7 +30,7 @@ def index():
|
||||
|
||||
@statistics_bp.route('/book-ranking')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def book_ranking():
|
||||
"""热门图书排行榜页面"""
|
||||
# 记录访问热门图书排行的日志
|
||||
@ -45,7 +45,7 @@ def book_ranking():
|
||||
|
||||
@statistics_bp.route('/api/book-ranking')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def api_book_ranking():
|
||||
"""获取热门图书排行数据API"""
|
||||
time_range = request.args.get('time_range', 'month')
|
||||
@ -98,7 +98,7 @@ def api_book_ranking():
|
||||
|
||||
@statistics_bp.route('/borrow-statistics')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def borrow_statistics():
|
||||
"""借阅统计分析页面"""
|
||||
# 记录访问借阅统计分析的日志
|
||||
@ -113,7 +113,7 @@ def borrow_statistics():
|
||||
|
||||
@statistics_bp.route('/api/borrow-trend')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def api_borrow_trend():
|
||||
"""获取借阅趋势数据API"""
|
||||
time_range = request.args.get('time_range', 'month')
|
||||
@ -252,7 +252,7 @@ def api_borrow_trend():
|
||||
|
||||
@statistics_bp.route('/api/category-distribution')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def api_category_distribution():
|
||||
"""获取图书分类分布数据API"""
|
||||
# 记录获取图书分类分布数据的日志
|
||||
@ -295,7 +295,7 @@ def api_category_distribution():
|
||||
|
||||
@statistics_bp.route('/user-activity')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def user_activity():
|
||||
"""用户活跃度分析页面"""
|
||||
# 记录访问用户活跃度分析的日志
|
||||
@ -310,7 +310,7 @@ def user_activity():
|
||||
|
||||
@statistics_bp.route('/api/user-activity')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def api_user_activity():
|
||||
"""获取用户活跃度数据API"""
|
||||
# 记录获取用户活跃度数据的日志
|
||||
@ -347,7 +347,7 @@ def api_user_activity():
|
||||
|
||||
@statistics_bp.route('/overdue-analysis')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def overdue_analysis():
|
||||
"""逾期分析页面"""
|
||||
# 记录访问逾期分析的日志
|
||||
@ -362,7 +362,7 @@ def overdue_analysis():
|
||||
|
||||
@statistics_bp.route('/api/overdue-statistics')
|
||||
@login_required
|
||||
@admin_required
|
||||
@permission_required('view_statistics') # 替代 @admin_required
|
||||
def api_overdue_statistics():
|
||||
"""获取逾期统计数据API"""
|
||||
# 记录获取逾期统计数据的日志
|
||||
|
||||
@ -492,21 +492,89 @@ def role_list():
|
||||
return render_template('user/roles.html', roles=roles)
|
||||
|
||||
|
||||
# 创建/编辑角色API
|
||||
# 获取所有系统权限的API
|
||||
@user_bp.route('/permissions', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def get_permissions():
|
||||
"""获取所有可用的系统权限"""
|
||||
from app.models.permission import Permission
|
||||
|
||||
try:
|
||||
permissions = Permission.query.order_by(Permission.code).all()
|
||||
|
||||
# 转换为JSON格式
|
||||
permissions_data = [{
|
||||
'id': p.id,
|
||||
'code': p.code,
|
||||
'name': p.name,
|
||||
'description': p.description
|
||||
} for p in permissions]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'permissions': permissions_data
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"获取权限列表失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f"获取权限列表失败: {str(e)}"
|
||||
}), 500
|
||||
|
||||
|
||||
# 获取特定角色的权限
|
||||
@user_bp.route('/role/<int:role_id>/permissions', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def get_role_permissions(role_id):
|
||||
"""获取指定角色的权限ID列表"""
|
||||
from app.models.user import Role
|
||||
|
||||
try:
|
||||
role = Role.query.get(role_id)
|
||||
if not role:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '角色不存在'
|
||||
}), 404
|
||||
|
||||
# 获取角色的所有权限ID
|
||||
permissions = [p.id for p in role.permissions]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'permissions': permissions
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"获取角色权限失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f"获取角色权限失败: {str(e)}"
|
||||
}), 500
|
||||
|
||||
|
||||
# 修改角色保存路由,支持权限管理
|
||||
@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')
|
||||
permission_ids = data.get('permissions', []) # 获取权限ID列表
|
||||
|
||||
if not role_name:
|
||||
return jsonify({'success': False, 'message': '角色名不能为空'})
|
||||
|
||||
if role_id: # 更新
|
||||
success, message = UserService.update_role(role_id, role_name, description)
|
||||
# 处理系统内置角色的权限保护
|
||||
if role_id and int(role_id) in [1, 2]:
|
||||
permission_ids = None # 不修改内置角色的权限
|
||||
|
||||
if role_id: # 更新角色
|
||||
success, message = UserService.update_role(role_id, role_name, description, permission_ids)
|
||||
if success:
|
||||
# 记录编辑角色日志
|
||||
Log.add_log(
|
||||
@ -515,15 +583,12 @@ def role_save():
|
||||
target_type="角色",
|
||||
target_id=role_id,
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员 {current_user.username} 编辑角色 {role_name}"
|
||||
description=f"管理员 {current_user.username} 编辑角色 {role_name},包含权限设置"
|
||||
)
|
||||
else: # 创建
|
||||
success, message = UserService.create_role(role_name, description)
|
||||
else: # 创建角色
|
||||
success, message, new_role_id = UserService.create_role(role_name, description, permission_ids)
|
||||
if success:
|
||||
# 获取新创建的角色ID
|
||||
new_role = db.session.query(User.Role).filter_by(role_name=role_name).first()
|
||||
role_id = new_role.id if new_role else None
|
||||
|
||||
role_id = new_role_id
|
||||
# 记录创建角色日志
|
||||
Log.add_log(
|
||||
action="创建角色",
|
||||
@ -531,13 +596,72 @@ def role_save():
|
||||
target_type="角色",
|
||||
target_id=role_id,
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员 {current_user.username} 创建新角色 {role_name}"
|
||||
description=f"管理员 {current_user.username} 创建新角色 {role_name},设置了 {len(permission_ids)} 个权限"
|
||||
)
|
||||
|
||||
return jsonify({'success': success, 'message': message})
|
||||
|
||||
|
||||
@user_bp.route('/user/role/<int:role_id>/count', methods=['GET'])
|
||||
# 角色删除API
|
||||
@user_bp.route('/role/delete/<int:role_id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def role_delete(role_id):
|
||||
"""删除角色"""
|
||||
# 保护系统内置角色
|
||||
if role_id in [1, 2]:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '不能删除系统内置角色'
|
||||
})
|
||||
|
||||
from app.models.user import Role
|
||||
|
||||
try:
|
||||
# 获取角色信息用于日志记录
|
||||
role = Role.query.get(role_id)
|
||||
if not role:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '角色不存在'
|
||||
}), 404
|
||||
|
||||
role_name = role.role_name
|
||||
|
||||
# 检查是否有用户在使用该角色
|
||||
user_count = User.query.filter_by(role_id=role_id).count()
|
||||
if user_count > 0:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'无法删除:该角色下存在 {user_count} 个用户'
|
||||
})
|
||||
|
||||
# 删除角色
|
||||
db.session.delete(role)
|
||||
db.session.commit()
|
||||
|
||||
# 记录删除角色日志
|
||||
Log.add_log(
|
||||
action="删除角色",
|
||||
user_id=current_user.id,
|
||||
target_type="角色",
|
||||
ip_address=request.remote_addr,
|
||||
description=f"管理员 {current_user.username} 删除了角色 {role_name}"
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '角色删除成功'
|
||||
})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logging.error(f"删除角色失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f"删除角色失败: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@user_bp.route('/role/<int:role_id>/count', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def get_role_user_count(role_id):
|
||||
|
||||
103
app/init_permissions.py
Normal file
103
app/init_permissions.py
Normal file
@ -0,0 +1,103 @@
|
||||
from app import create_app
|
||||
from app.models.database import db
|
||||
from app.models.user import Role
|
||||
from app.models.permission import Permission
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def init_permissions():
|
||||
"""初始化系统权限"""
|
||||
logger.info("开始初始化系统权限...")
|
||||
|
||||
# 只定义管理类权限,对应现有的 @admin_required 装饰的路由
|
||||
permissions = [
|
||||
# 公告管理权限
|
||||
{'code': 'manage_announcements', 'name': '公告管理', 'description': '允许管理系统公告(发布、编辑、删除、置顶等)'},
|
||||
|
||||
# 图书管理权限
|
||||
{'code': 'manage_books', 'name': '图书管理', 'description': '允许管理图书(添加、编辑、删除图书)'},
|
||||
{'code': 'manage_categories', 'name': '分类管理', 'description': '允许管理图书分类'},
|
||||
{'code': 'import_export_books', 'name': '导入导出图书', 'description': '允许批量导入和导出图书数据'},
|
||||
|
||||
# 借阅管理权限
|
||||
{'code': 'manage_borrows', 'name': '借阅管理', 'description': '允许管理全系统借阅记录和处理借还书操作'},
|
||||
{'code': 'manage_overdue', 'name': '逾期管理', 'description': '允许查看和处理逾期借阅'},
|
||||
|
||||
# 库存管理权限
|
||||
{'code': 'manage_inventory', 'name': '库存管理', 'description': '允许查看和调整图书库存'},
|
||||
|
||||
# 日志权限
|
||||
{'code': 'view_logs', 'name': '查看日志', 'description': '允许查看系统操作日志'},
|
||||
|
||||
# 统计权限
|
||||
{'code': 'view_statistics', 'name': '查看统计', 'description': '允许查看统计分析数据'},
|
||||
|
||||
# 用户管理权限
|
||||
{'code': 'manage_users', 'name': '用户管理', 'description': '允许管理用户(添加、编辑、禁用、删除用户)'},
|
||||
{'code': 'manage_roles', 'name': '角色管理', 'description': '允许管理角色和权限'},
|
||||
]
|
||||
|
||||
# 添加权限记录
|
||||
added_count = 0
|
||||
updated_count = 0
|
||||
|
||||
for perm_data in permissions:
|
||||
# 检查权限是否已存在
|
||||
existing_perm = Permission.query.filter_by(code=perm_data['code']).first()
|
||||
|
||||
if existing_perm:
|
||||
# 更新现有权限信息
|
||||
existing_perm.name = perm_data['name']
|
||||
existing_perm.description = perm_data['description']
|
||||
updated_count += 1
|
||||
else:
|
||||
# 创建新权限
|
||||
permission = Permission(**perm_data)
|
||||
db.session.add(permission)
|
||||
added_count += 1
|
||||
|
||||
# 提交所有权限
|
||||
db.session.commit()
|
||||
logger.info(f"权限初始化完成: 新增 {added_count} 个, 更新 {updated_count} 个")
|
||||
|
||||
# 处理角色权限分配
|
||||
assign_role_permissions()
|
||||
|
||||
|
||||
def assign_role_permissions():
|
||||
"""为系统默认角色分配权限"""
|
||||
logger.info("开始分配角色权限...")
|
||||
|
||||
# 获取所有权限
|
||||
all_permissions = Permission.query.all()
|
||||
|
||||
# 获取系统内置角色
|
||||
admin_role = Role.query.get(1) # 管理员角色
|
||||
user_role = Role.query.get(2) # 普通用户角色
|
||||
|
||||
if admin_role and user_role:
|
||||
# 管理员拥有所有权限
|
||||
admin_role.permissions = all_permissions
|
||||
|
||||
# 普通用户无需特殊管理权限
|
||||
user_role.permissions = []
|
||||
|
||||
db.session.commit()
|
||||
logger.info(f"管理员角色分配了 {len(all_permissions)} 个权限")
|
||||
logger.info(f"普通用户角色无管理权限")
|
||||
else:
|
||||
logger.error("无法找到内置角色,请确保角色表已正确初始化")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
init_permissions()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
app/models/database.py
Normal file
4
app/models/database.py
Normal file
@ -0,0 +1,4 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# 创建共享的SQLAlchemy实例
|
||||
db = SQLAlchemy()
|
||||
20
app/models/permission.py
Normal file
20
app/models/permission.py
Normal file
@ -0,0 +1,20 @@
|
||||
from app.models.database import db
|
||||
from datetime import datetime
|
||||
|
||||
# 这是权限表 model
|
||||
class Permission(db.Model):
|
||||
__tablename__ = 'permissions'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String(64), unique=True, nullable=False, comment='权限代码,用于系统识别')
|
||||
name = db.Column(db.String(64), nullable=False, comment='权限名称,用于界面显示')
|
||||
description = db.Column(db.String(255), comment='权限描述,说明权限用途')
|
||||
|
||||
# 角色-权限 关联表(辅助对象模式,方便ORM关系管理)
|
||||
class RolePermission(db.Model):
|
||||
__tablename__ = 'role_permissions'
|
||||
|
||||
role_id = db.Column(db.Integer, db.ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True, comment='角色ID,关联roles表')
|
||||
permission_id = db.Column(db.Integer, db.ForeignKey('permissions.id', ondelete='CASCADE'), primary_key=True, comment='权限ID,关联permissions表')
|
||||
created_at = db.Column(db.DateTime, default=datetime.now, comment='权限分配时间')
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from app.models.database import db
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from datetime import datetime
|
||||
from flask_login import UserMixin
|
||||
from app.models.permission import RolePermission, Permission
|
||||
|
||||
db = SQLAlchemy()
|
||||
#db = SQLAlchemy()
|
||||
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
@ -77,4 +78,11 @@ class Role(db.Model):
|
||||
role_name = db.Column(db.String(32), unique=True, nullable=False)
|
||||
description = db.Column(db.String(128))
|
||||
|
||||
permissions = db.relationship(
|
||||
'Permission',
|
||||
secondary='role_permissions',
|
||||
backref=db.backref('roles', lazy='dynamic'),
|
||||
lazy='dynamic'
|
||||
)
|
||||
|
||||
users = db.relationship('User', backref='role')
|
||||
|
||||
@ -125,37 +125,58 @@ class UserService:
|
||||
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, "角色名已存在"
|
||||
def create_role(role_name, description, permission_ids=None):
|
||||
"""创建新角色,包括权限分配"""
|
||||
from app.models.user import Role
|
||||
from app.models.permission import Permission
|
||||
|
||||
# 检查角色名是否已存在
|
||||
if Role.query.filter_by(role_name=role_name).first():
|
||||
return False, "角色名称已存在", None
|
||||
|
||||
role = Role(role_name=role_name, description=description)
|
||||
|
||||
# 添加权限
|
||||
if permission_ids:
|
||||
permissions = Permission.query.filter(Permission.id.in_(permission_ids)).all()
|
||||
role.permissions = permissions
|
||||
|
||||
try:
|
||||
role = Role(role_name=role_name, description=description)
|
||||
db.session.add(role)
|
||||
db.session.commit()
|
||||
return True, "角色创建成功"
|
||||
return True, "角色创建成功", role.id
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return False, f"创建失败: {str(e)}"
|
||||
return False, f"创建失败: {str(e)}", None
|
||||
|
||||
@staticmethod
|
||||
def update_role(role_id, role_name, description=None):
|
||||
"""更新角色信息"""
|
||||
def update_role(role_id, role_name, description, permission_ids=None):
|
||||
"""更新角色信息,包括权限"""
|
||||
from app.models.user import Role
|
||||
from app.models.permission import Permission
|
||||
|
||||
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, "角色名已存在"
|
||||
# 系统内置角色不允许修改名称
|
||||
if role_id in [1, 2] and role.role_name != role_name:
|
||||
return False, "系统内置角色不允许修改名称"
|
||||
|
||||
# 检查角色名是否已存在
|
||||
existing_role = Role.query.filter(Role.role_name == role_name, Role.id != role_id).first()
|
||||
if existing_role:
|
||||
return False, "角色名称已存在"
|
||||
|
||||
role.role_name = role_name
|
||||
role.description = description
|
||||
|
||||
# 更新权限(如果提供了权限列表且不是内置角色)
|
||||
if permission_ids is not None and role_id not in [1, 2]:
|
||||
permissions = Permission.query.filter(Permission.id.in_(permission_ids)).all()
|
||||
role.permissions = permissions
|
||||
|
||||
try:
|
||||
role.role_name = role_name
|
||||
if description is not None:
|
||||
role.description = description
|
||||
db.session.commit()
|
||||
return True, "角色更新成功"
|
||||
except Exception as e:
|
||||
@ -182,3 +203,4 @@ class UserService:
|
||||
db.session.rollback()
|
||||
logging.error(f"创建用户失败: {str(e)}")
|
||||
return False, f'创建用户失败: {str(e)}'
|
||||
|
||||
|
||||
@ -251,3 +251,94 @@
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 权限选择部分样式 */
|
||||
.permissions-container {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.permission-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.permission-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.permission-group-title {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.permission-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.permission-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.permission-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.permission-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.permission-description {
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-permissions {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* 将模态框调整为大一点 */
|
||||
.modal-lg {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* 权限项样式 */
|
||||
.permission-item {
|
||||
position: relative;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.permission-item:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.permission-item label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.permission-item input[type="checkbox"] {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
@ -16,6 +16,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 加载角色用户统计
|
||||
fetchRoleUserCounts();
|
||||
|
||||
// 初始化时加载权限列表
|
||||
loadPermissions();
|
||||
|
||||
// 添加角色按钮点击事件
|
||||
if (addRoleBtn) {
|
||||
addRoleBtn.addEventListener('click', function() {
|
||||
@ -27,6 +30,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 更新模态框标题
|
||||
document.getElementById('roleModalLabel').textContent = '添加角色';
|
||||
|
||||
// 启用所有权限复选框
|
||||
document.querySelectorAll('.permission-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = false;
|
||||
});
|
||||
|
||||
// 隐藏系统角色警告
|
||||
const systemRoleAlert = document.getElementById('systemRoleAlert');
|
||||
if (systemRoleAlert) {
|
||||
systemRoleAlert.style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示模态框
|
||||
roleModal.modal('show');
|
||||
});
|
||||
@ -54,6 +69,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 更新模态框标题
|
||||
document.getElementById('roleModalLabel').textContent = '编辑角色';
|
||||
|
||||
// 加载角色权限
|
||||
loadRolePermissions(roleId);
|
||||
|
||||
// 显示模态框
|
||||
roleModal.modal('show');
|
||||
});
|
||||
@ -79,10 +97,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集选中的权限ID
|
||||
const permissionIds = [];
|
||||
document.querySelectorAll('.permission-checkbox:checked:not(:disabled)').forEach(checkbox => {
|
||||
permissionIds.push(parseInt(checkbox.value));
|
||||
});
|
||||
|
||||
const roleData = {
|
||||
id: roleIdInput.value || null,
|
||||
role_name: roleNameInput.value.trim(),
|
||||
description: roleDescriptionInput.value.trim() || null
|
||||
description: roleDescriptionInput.value.trim() || null,
|
||||
permissions: permissionIds
|
||||
};
|
||||
|
||||
saveRole(roleData);
|
||||
@ -99,6 +124,182 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// 加载权限列表
|
||||
function loadPermissions() {
|
||||
// 获取权限容器
|
||||
const bookPermissions = document.getElementById('book-permissions');
|
||||
const userPermissions = document.getElementById('user-permissions');
|
||||
const borrowPermissions = document.getElementById('borrow-permissions');
|
||||
const systemPermissions = document.getElementById('system-permissions');
|
||||
|
||||
if (!bookPermissions) return; // 如果元素不存在就退出
|
||||
|
||||
// 设置加载中状态
|
||||
bookPermissions.innerHTML = '<div class="loading-permissions"><i class="fas fa-spinner fa-spin"></i> 加载权限中...</div>';
|
||||
userPermissions.innerHTML = '';
|
||||
borrowPermissions.innerHTML = '';
|
||||
systemPermissions.innerHTML = '';
|
||||
|
||||
// 获取权限数据
|
||||
fetch('/user/permissions', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应异常');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// 清除加载中状态
|
||||
bookPermissions.innerHTML = '';
|
||||
|
||||
// 按分组整理权限
|
||||
const permissionGroups = {
|
||||
book: [], // 图书相关权限
|
||||
user: [], // 用户相关权限
|
||||
borrow: [], // 借阅相关权限
|
||||
system: [] // 系统相关权限
|
||||
};
|
||||
|
||||
// 定义权限分组映射
|
||||
const permGroupMap = {
|
||||
'manage_books': 'book',
|
||||
'manage_categories': 'book',
|
||||
'import_export_books': 'book',
|
||||
|
||||
'manage_users': 'user',
|
||||
'manage_roles': 'user',
|
||||
|
||||
'manage_borrows': 'borrow',
|
||||
'manage_overdue': 'borrow',
|
||||
|
||||
// 系统相关权限
|
||||
'manage_announcements': 'system',
|
||||
'manage_inventory': 'system',
|
||||
'view_logs': 'system',
|
||||
'view_statistics': 'system'
|
||||
};
|
||||
|
||||
// 根据映射表分组
|
||||
data.permissions.forEach(perm => {
|
||||
const group = permGroupMap[perm.code] || 'system';
|
||||
permissionGroups[group].push(perm);
|
||||
});
|
||||
|
||||
// 渲染各组权限
|
||||
renderPermissionGroup(bookPermissions, permissionGroups.book);
|
||||
renderPermissionGroup(userPermissions, permissionGroups.user);
|
||||
renderPermissionGroup(borrowPermissions, permissionGroups.borrow);
|
||||
renderPermissionGroup(systemPermissions, permissionGroups.system);
|
||||
} else {
|
||||
bookPermissions.innerHTML = '<div class="text-danger">加载权限失败</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
bookPermissions.innerHTML = '<div class="text-danger">加载权限失败,请刷新页面重试</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染权限组
|
||||
function renderPermissionGroup(container, permissions) {
|
||||
if (permissions.length === 0) {
|
||||
container.innerHTML = '<div class="text-muted">暂无相关权限</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
permissions.forEach(perm => {
|
||||
html += `
|
||||
<div class="permission-item">
|
||||
<input type="checkbox" id="perm_${perm.id}" class="permission-checkbox" value="${perm.id}">
|
||||
<label for="perm_${perm.id}">
|
||||
<span class="permission-name">${perm.name}</span>
|
||||
<span class="permission-description">${perm.description || '无描述'}</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// 加载角色的权限
|
||||
function loadRolePermissions(roleId) {
|
||||
if (!roleId) return;
|
||||
|
||||
// 先清空所有已选权限
|
||||
document.querySelectorAll('.permission-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
});
|
||||
|
||||
// 如果是系统内置角色,显示警告并禁用权限选择
|
||||
const isSystemRole = (roleId == 1 || roleId == 2);
|
||||
const systemRoleAlert = document.getElementById('systemRoleAlert');
|
||||
const permissionCheckboxes = document.querySelectorAll('.permission-checkbox');
|
||||
|
||||
if (systemRoleAlert) {
|
||||
systemRoleAlert.style.display = isSystemRole ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 管理员角色自动选中所有权限并禁用
|
||||
if (roleId == 1) { // 管理员
|
||||
permissionCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
checkbox.disabled = true;
|
||||
});
|
||||
return;
|
||||
} else if (roleId == 2) { // 普通用户,只有基本权限
|
||||
permissionCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = true; // 普通用户权限不可修改
|
||||
});
|
||||
|
||||
// 获取普通用户已分配的权限
|
||||
fetch(`/user/role/${roleId}/permissions`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
permissionCheckboxes.forEach(checkbox => {
|
||||
// 如果权限ID在返回的列表中,则选中
|
||||
checkbox.checked = data.permissions.includes(parseInt(checkbox.value));
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 为自定义角色加载并选中权限
|
||||
fetch(`/user/role/${roleId}/permissions`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
permissionCheckboxes.forEach(checkbox => {
|
||||
// 启用所有复选框
|
||||
checkbox.disabled = false;
|
||||
|
||||
// 如果权限ID在返回的列表中,则选中
|
||||
checkbox.checked = data.permissions.includes(parseInt(checkbox.value));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 保存角色
|
||||
function saveRole(roleData) {
|
||||
// 显示加载状态
|
||||
|
||||
@ -135,7 +135,7 @@
|
||||
|
||||
<!-- 角色编辑模态框 -->
|
||||
<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-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="roleModalLabel">添加角色</h5>
|
||||
@ -156,6 +156,51 @@
|
||||
<label for="roleDescription">角色描述</label>
|
||||
<textarea class="form-control" id="roleDescription" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 权限选择部分 -->
|
||||
<div class="form-group" id="permissionsSection">
|
||||
<label>角色权限</label>
|
||||
<div class="alert alert-info" id="systemRoleAlert" style="display: none;">
|
||||
<i class="fas fa-info-circle"></i> 系统内置角色的权限配置受到限制。
|
||||
</div>
|
||||
|
||||
<div class="permissions-container">
|
||||
<!-- 权限分组:图书管理 -->
|
||||
<div class="permission-group">
|
||||
<h6 class="permission-group-title">图书管理</h6>
|
||||
<div class="permission-items" id="book-permissions">
|
||||
<!-- 权限项将通过JS动态加载 -->
|
||||
<div class="loading-permissions">
|
||||
<i class="fas fa-spinner fa-spin"></i> 加载权限中...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权限分组:用户管理 -->
|
||||
<div class="permission-group">
|
||||
<h6 class="permission-group-title">用户管理</h6>
|
||||
<div class="permission-items" id="user-permissions">
|
||||
<!-- 权限项将通过JS动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权限分组:借阅管理 -->
|
||||
<div class="permission-group">
|
||||
<h6 class="permission-group-title">借阅管理</h6>
|
||||
<div class="permission-items" id="borrow-permissions">
|
||||
<!-- 权限项将通过JS动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权限分组:系统管理 -->
|
||||
<div class="permission-group">
|
||||
<h6 class="permission-group-title">系统管理</h6>
|
||||
<div class="permission-items" id="system-permissions">
|
||||
<!-- 权限项将通过JS动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@ -30,3 +30,52 @@ def admin_required(f):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
def permission_required(permission_code):
|
||||
"""
|
||||
检查用户是否拥有特定权限的装饰器
|
||||
:param permission_code: 权限代码,例如 'manage_books'
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
print(
|
||||
f"DEBUG: permission_required({permission_code}) 检查 - current_user.is_authenticated = {current_user.is_authenticated}")
|
||||
|
||||
# 检查用户是否登录
|
||||
if not current_user.is_authenticated:
|
||||
flash('请先登录', 'warning')
|
||||
return redirect(url_for('user.login', next=request.url))
|
||||
|
||||
# 管理员拥有所有权限
|
||||
if getattr(current_user, 'role_id', None) == 1:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
# 获取用户角色并检查是否有指定权限
|
||||
from app.models.user import Role
|
||||
role = Role.query.get(current_user.role_id)
|
||||
|
||||
if not role:
|
||||
flash('用户角色异常', 'danger')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# 检查角色是否有指定权限
|
||||
has_permission = False
|
||||
for perm in role.permissions:
|
||||
if perm.code == permission_code:
|
||||
has_permission = True
|
||||
break
|
||||
|
||||
if not has_permission:
|
||||
print(f"DEBUG: 用户 {current_user.username} 缺少权限 {permission_code}")
|
||||
flash('您没有执行此操作的权限', 'danger')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,45 @@ CREATE TABLE `users` (
|
||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`)
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for permissions
|
||||
-- ----------------------------
|
||||
-- 这是权限表,用于存储系统中所有可分配的权限
|
||||
-- id: 权限的唯一标识符,自增主键
|
||||
-- code: 权限的唯一代码,用于程序中进行权限验证,如'manage_books'、'view_users'等
|
||||
-- name: 权限的显示名称,便于管理员理解,如'管理图书'、'查看用户'等
|
||||
-- description: 权限的详细描述,解释该权限允许用户执行什么操作
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限代码,用于系统识别',
|
||||
`name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限名称,用于界面显示',
|
||||
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限描述,说明权限用途',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `code` (`code`) COMMENT '权限代码必须唯一'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统权限表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for role_permissions
|
||||
-- ----------------------------
|
||||
-- 这是角色-权限关联表,用于建立角色和权限之间的多对多关系
|
||||
-- role_id: 角色ID,外键关联到roles表
|
||||
-- permission_id: 权限ID,外键关联到permissions表
|
||||
-- created_at: 记录权限分配给角色的时间
|
||||
-- 该表使用role_id和permission_id的组合作为复合主键,确保一个角色不会重复分配同一权限
|
||||
DROP TABLE IF EXISTS `role_permissions`;
|
||||
CREATE TABLE `role_permissions` (
|
||||
`role_id` int NOT NULL COMMENT '角色ID,关联roles表的id字段',
|
||||
`permission_id` int NOT NULL COMMENT '权限ID,关联permissions表的id字段',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '权限分配时间',
|
||||
PRIMARY KEY (`role_id`,`permission_id`), -- 复合主键,确保一个角色不会重复分配同一权限
|
||||
KEY `permission_id` (`permission_id`), -- 权限ID索引,提高查询效率
|
||||
CONSTRAINT `role_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `role_permissions_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色-权限关联表';
|
||||
|
||||
|
||||
|
||||
-- (可选)初始化角色数据
|
||||
INSERT INTO `roles` (`role_name`, `description`) VALUES
|
||||
('admin', '管理员'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user