feat: v3.2 - 用户管理功能和后台菜单统一
新增功能: - 用户管理列表页面(搜索、分页) - 用户详情页面(基本信息、收藏统计) - 管理员重置用户密码功能 - 管理员修改用户昵称功能 - 管理后台首页添加用户统计卡片 优化改进: - 统一后台菜单结构,创建可复用的 sidebar 组件 - 所有后台页面使用统一菜单,避免硬编码 - 优化权限配置文件,清理冗余规则 技术文档: - 添加任务分解规则文档 - 添加后台菜单统一规则文档 - 添加数据库字段修复脚本 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
171
app.py
171
app.py
@@ -2518,6 +2518,174 @@ Sitemap: {}sitemap.xml
|
||||
|
||||
return render_template('admin/batch_import.html', results=results)
|
||||
|
||||
# ========== 用户管理路由 ==========
|
||||
@app.route('/admin/users')
|
||||
@login_required
|
||||
def admin_users():
|
||||
"""用户管理列表页"""
|
||||
if not isinstance(current_user, AdminModel):
|
||||
flash('无权访问', 'error')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# 获取分页参数
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 20
|
||||
|
||||
# 获取搜索参数
|
||||
search = request.args.get('search', '').strip()
|
||||
|
||||
# 构建查询
|
||||
query = User.query
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
User.username.like(f'%{search}%'),
|
||||
User.email.like(f'%{search}%')
|
||||
)
|
||||
)
|
||||
|
||||
# 排序:按注册时间倒序
|
||||
query = query.order_by(User.created_at.desc())
|
||||
|
||||
# 分页
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
users = pagination.items
|
||||
|
||||
# 为每个用户统计收藏数据
|
||||
user_stats = {}
|
||||
for user in users:
|
||||
user_stats[user.id] = {
|
||||
'collections_count': Collection.query.filter_by(user_id=user.id).count(),
|
||||
'folders_count': Folder.query.filter_by(user_id=user.id).count()
|
||||
}
|
||||
|
||||
# 获取真实的 admin 实例
|
||||
from flask import current_app
|
||||
admin_instance = current_app.extensions['admin'][0]
|
||||
|
||||
# 创建模拟的 admin_view 对象
|
||||
class MockAdminView:
|
||||
name = '用户管理'
|
||||
category = None
|
||||
admin = admin_instance
|
||||
|
||||
return render_template('admin/users/list.html',
|
||||
users=users,
|
||||
pagination=pagination,
|
||||
user_stats=user_stats,
|
||||
search=search,
|
||||
admin_view=MockAdminView())
|
||||
|
||||
@app.route('/admin/users/<int:user_id>')
|
||||
@login_required
|
||||
def admin_user_detail(user_id):
|
||||
"""用户详情页"""
|
||||
if not isinstance(current_user, AdminModel):
|
||||
flash('无权访问', 'error')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# 统计数据
|
||||
collections_count = Collection.query.filter_by(user_id=user.id).count()
|
||||
folders_count = Folder.query.filter_by(user_id=user.id).count()
|
||||
|
||||
# 获取最近的收藏(前10条)
|
||||
recent_collections = Collection.query.filter_by(user_id=user.id)\
|
||||
.order_by(Collection.created_at.desc())\
|
||||
.limit(10).all()
|
||||
|
||||
# 获取所有文件夹
|
||||
folders = Folder.query.filter_by(user_id=user.id)\
|
||||
.order_by(Folder.sort_order.desc(), Folder.created_at.desc())\
|
||||
.all()
|
||||
|
||||
# 获取真实的 admin 实例
|
||||
from flask import current_app
|
||||
admin_instance = current_app.extensions['admin'][0]
|
||||
|
||||
# 创建模拟的 admin_view 对象
|
||||
class MockAdminView:
|
||||
name = '用户详情'
|
||||
category = None
|
||||
admin = admin_instance
|
||||
|
||||
return render_template('admin/users/detail.html',
|
||||
user=user,
|
||||
collections_count=collections_count,
|
||||
folders_count=folders_count,
|
||||
recent_collections=recent_collections,
|
||||
folders=folders,
|
||||
admin_view=MockAdminView())
|
||||
|
||||
@app.route('/api/admin/users/<int:user_id>/reset-password', methods=['POST'])
|
||||
@login_required
|
||||
def admin_reset_user_password(user_id):
|
||||
"""管理员重置用户密码"""
|
||||
if not isinstance(current_user, AdminModel):
|
||||
return jsonify({'success': False, 'message': '无权操作'}), 403
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
data = request.get_json()
|
||||
new_password = data.get('new_password', '').strip()
|
||||
|
||||
# 验证新密码
|
||||
if not new_password:
|
||||
return jsonify({'success': False, 'message': '新密码不能为空'}), 400
|
||||
|
||||
if len(new_password) < 6:
|
||||
return jsonify({'success': False, 'message': '新密码至少需要6位'}), 400
|
||||
|
||||
try:
|
||||
# 设置新密码
|
||||
user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'已成功重置用户 {user.username} 的密码'
|
||||
})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'message': f'重置密码失败: {str(e)}'}), 500
|
||||
|
||||
@app.route('/api/admin/users/<int:user_id>/update-username', methods=['POST'])
|
||||
@login_required
|
||||
def admin_update_username(user_id):
|
||||
"""管理员修改用户昵称"""
|
||||
if not isinstance(current_user, AdminModel):
|
||||
return jsonify({'success': False, 'message': '无权操作'}), 403
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
data = request.get_json()
|
||||
new_username = data.get('new_username', '').strip()
|
||||
|
||||
# 验证新昵称
|
||||
if not new_username:
|
||||
return jsonify({'success': False, 'message': '昵称不能为空'}), 400
|
||||
|
||||
if len(new_username) < 2 or len(new_username) > 50:
|
||||
return jsonify({'success': False, 'message': '昵称长度需要在2-50个字符之间'}), 400
|
||||
|
||||
# 检查昵称是否已被使用
|
||||
existing_user = User.query.filter_by(username=new_username).first()
|
||||
if existing_user and existing_user.id != user.id:
|
||||
return jsonify({'success': False, 'message': '该昵称已被使用'}), 400
|
||||
|
||||
try:
|
||||
old_username = user.username
|
||||
user.username = new_username
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'已成功将昵称从 {old_username} 修改为 {new_username}'
|
||||
})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'message': f'修改昵称失败: {str(e)}'}), 500
|
||||
|
||||
# ========== Flask-Admin 配置 ==========
|
||||
class SecureModelView(ModelView):
|
||||
"""需要登录的模型视图"""
|
||||
@@ -2557,7 +2725,8 @@ Sitemap: {}sitemap.xml
|
||||
'sites_count': Site.query.filter_by(is_active=True).count(),
|
||||
'tags_count': Tag.query.count(),
|
||||
'news_count': News.query.filter_by(is_active=True).count(),
|
||||
'total_views': db.session.query(db.func.sum(Site.view_count)).scalar() or 0
|
||||
'total_views': db.session.query(db.func.sum(Site.view_count)).scalar() or 0,
|
||||
'users_count': User.query.count()
|
||||
}
|
||||
|
||||
# 最近添加的工具(最多5个)
|
||||
|
||||
Reference in New Issue
Block a user