feat: v3.2 - 用户管理功能和后台菜单统一

新增功能:
- 用户管理列表页面(搜索、分页)
- 用户详情页面(基本信息、收藏统计)
- 管理员重置用户密码功能
- 管理员修改用户昵称功能
- 管理后台首页添加用户统计卡片

优化改进:
- 统一后台菜单结构,创建可复用的 sidebar 组件
- 所有后台页面使用统一菜单,避免硬编码
- 优化权限配置文件,清理冗余规则

技术文档:
- 添加任务分解规则文档
- 添加后台菜单统一规则文档
- 添加数据库字段修复脚本

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jowe
2026-02-08 23:20:35 +08:00
parent c61969dfc9
commit 2eefaa8cc9
14 changed files with 2168 additions and 297 deletions

171
app.py
View File

@@ -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个