新增个人资料功能

This commit is contained in:
rjb
2025-08-29 20:32:52 +08:00
parent cb0702e9f5
commit 0c321333cc
4 changed files with 471 additions and 0 deletions

View File

@@ -92,6 +92,11 @@ def get_profile():
"""获取个人资料API"""
user_id = session['user_id']
result = AuthService.get_user_by_id(user_id)
if result['success']:
return jsonify({
'success': True,
'data': result['user']
})
return jsonify(result)
@auth_bp.route('/api/profile', methods=['PUT'])
@@ -147,3 +152,57 @@ def check_login():
'success': True,
'logged_in': False
})
@auth_bp.route('/api/profile/stats', methods=['GET'])
@login_required
def get_profile_stats():
"""获取用户使用统计API"""
user_id = session['user_id']
try:
from src.flask_prompt_master.models.models import Prompt
from src.flask_prompt_master.models.favorites import Favorite
from sqlalchemy import func
# 统计生成的提示词数量
total_prompts = Prompt.query.filter_by(user_id=user_id).count()
# 统计收藏数量
total_favorites = Favorite.query.filter_by(user_id=str(user_id)).count()
# 计算活跃天数(从注册时间到现在)
from src.flask_prompt_master.models.models import User
user = User.query.get(user_id)
days_active = 0
if user and user.created_time:
from datetime import datetime
days_active = (datetime.now() - user.created_time).days + 1
# 获取最近活动
recent_prompts = Prompt.query.filter_by(user_id=user_id)\
.order_by(Prompt.created_at.desc())\
.limit(5).all()
recent_activities = []
for prompt in recent_prompts:
recent_activities.append({
'type': '生成提示词',
'content': prompt.input_text[:30] + '...' if len(prompt.input_text) > 30 else prompt.input_text,
'time': prompt.created_at.strftime('%Y-%m-%d %H:%M:%S') if prompt.created_at else ''
})
return jsonify({
'success': True,
'data': {
'total_prompts': total_prompts,
'total_favorites': total_favorites,
'days_active': days_active,
'recent_activities': recent_activities
}
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取统计信息失败: {str(e)}'
})

View File

@@ -0,0 +1,408 @@
{% extends "base.html" %}
{% block title %}个人资料 - 提示词大师{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h5 class="mb-0">个人中心</h5>
</div>
<div class="card-body">
<div class="nav flex-column nav-pills" id="profile-tab" role="tablist">
<button class="nav-link active" id="profile-info-tab" data-bs-toggle="pill" data-bs-target="#profile-info" type="button" role="tab">
<i class="fas fa-user me-2"></i>基本信息
</button>
<button class="nav-link" id="change-password-tab" data-bs-toggle="pill" data-bs-target="#change-password" type="button" role="tab">
<i class="fas fa-lock me-2"></i>修改密码
</button>
<button class="nav-link" id="usage-stats-tab" data-bs-toggle="pill" data-bs-target="#usage-stats" type="button" role="tab">
<i class="fas fa-chart-bar me-2"></i>使用统计
</button>
</div>
</div>
</div>
</div>
<!-- 主内容区 -->
<div class="col-md-9">
<div class="tab-content" id="profile-tabContent">
<!-- 基本信息 -->
<div class="tab-pane fade show active" id="profile-info" role="tabpanel">
<div class="card">
<div class="card-header">
<h4 class="mb-0">基本信息</h4>
</div>
<div class="card-body">
<form id="profile-form">
<div class="row">
<div class="col-md-6 mb-3">
<label for="nickname" class="form-label">昵称 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="nickname" name="nickname" required>
</div>
<div class="col-md-6 mb-3">
<label for="login_name" class="form-label">用户名</label>
<input type="text" class="form-control" id="login_name" readonly>
<small class="text-muted">用户名不可修改</small>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">邮箱</label>
<input type="email" class="form-control" id="email" name="email">
</div>
<div class="col-md-6 mb-3">
<label for="mobile" class="form-label">手机号</label>
<input type="tel" class="form-control" id="mobile" name="mobile">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="sex" class="form-label">性别</label>
<select class="form-select" id="sex" name="sex">
<option value="0">保密</option>
<option value="1"></option>
<option value="2"></option>
</select>
</div>
<div class="col-md-6 mb-3">
<label for="avatar" class="form-label">头像</label>
<div class="d-flex align-items-center">
<img id="avatar-preview" src="/static/images/default-avatar.png" alt="头像" class="rounded-circle me-3" style="width: 60px; height: 60px; object-fit: cover;">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="document.getElementById('avatar-input').click()">
更换头像
</button>
<input type="file" id="avatar-input" accept="image/*" style="display: none;">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="created_time" class="form-label">注册时间</label>
<input type="text" class="form-control" id="created_time" readonly>
</div>
<div class="col-md-6 mb-3">
<label for="last_login" class="form-label">最后登录</label>
<input type="text" class="form-control" id="last_login" readonly>
</div>
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>保存修改
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 修改密码 -->
<div class="tab-pane fade" id="change-password" role="tabpanel">
<div class="card">
<div class="card-header">
<h4 class="mb-0">修改密码</h4>
</div>
<div class="card-body">
<form id="password-form">
<div class="mb-3">
<label for="old_password" class="form-label">当前密码 <span class="text-danger">*</span></label>
<input type="password" class="form-control" id="old_password" name="old_password" required>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">新密码 <span class="text-danger">*</span></label>
<input type="password" class="form-control" id="new_password" name="new_password" required>
<div class="form-text">密码长度至少6位建议包含字母、数字和特殊字符</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">确认新密码 <span class="text-danger">*</span></label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
</div>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-warning">
<i class="fas fa-key me-2"></i>修改密码
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 使用统计 -->
<div class="tab-pane fade" id="usage-stats" role="tabpanel">
<div class="card">
<div class="card-header">
<h4 class="mb-0">使用统计</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 mb-3">
<div class="card bg-primary text-white">
<div class="card-body text-center">
<h3 id="total-prompts">0</h3>
<p class="mb-0">生成提示词</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h3 id="total-favorites">0</h3>
<p class="mb-0">收藏数量</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<h3 id="days-active">0</h3>
<p class="mb-0">活跃天数</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<h5>最近活动</h5>
<div id="recent-activity">
<p class="text-muted">加载中...</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 成功提示模态框 -->
<div class="modal fade" id="successModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">操作成功</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p id="success-message">操作已成功完成</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">确定</button>
</div>
</div>
</div>
</div>
<!-- 错误提示模态框 -->
<div class="modal fade" id="errorModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">操作失败</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p id="error-message">操作失败,请重试</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">确定</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// 页面加载完成后执行
$(document).ready(function() {
loadProfile();
loadUsageStats();
// 基本信息表单提交
$('#profile-form').submit(function(e) {
e.preventDefault();
updateProfile();
});
// 修改密码表单提交
$('#password-form').submit(function(e) {
e.preventDefault();
changePassword();
});
// 头像上传
$('#avatar-input').change(function() {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
$('#avatar-preview').attr('src', e.target.result);
};
reader.readAsDataURL(file);
}
});
});
// 加载个人资料
function loadProfile() {
$.get('/api/profile')
.done(function(response) {
if (response.success) {
const user = response.data;
$('#nickname').val(user.nickname || '');
$('#login_name').val(user.login_name || '');
$('#email').val(user.email || '');
$('#mobile').val(user.mobile || '');
$('#sex').val(user.sex || 0);
$('#created_time').val(user.created_time || '');
$('#last_login').val(user.last_login || '');
if (user.avatar) {
$('#avatar-preview').attr('src', user.avatar);
}
} else {
showError(response.message || '加载个人资料失败');
}
})
.fail(function() {
showError('加载个人资料失败');
});
}
// 更新个人资料
function updateProfile() {
const formData = {
nickname: $('#nickname').val(),
email: $('#email').val(),
mobile: $('#mobile').val(),
sex: parseInt($('#sex').val())
};
$.ajax({
url: '/api/profile',
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(formData)
})
.done(function(response) {
if (response.success) {
showSuccess('个人资料更新成功');
// 更新session中的昵称
if (formData.nickname) {
// 这里可以更新页面上的用户昵称显示
}
} else {
showError(response.message || '更新失败');
}
})
.fail(function() {
showError('更新失败,请重试');
});
}
// 修改密码
function changePassword() {
const oldPassword = $('#old_password').val();
const newPassword = $('#new_password').val();
const confirmPassword = $('#confirm_password').val();
if (newPassword !== confirmPassword) {
showError('两次输入的新密码不一致');
return;
}
if (newPassword.length < 6) {
showError('新密码长度至少6位');
return;
}
$.ajax({
url: '/api/change-password',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
old_password: oldPassword,
new_password: newPassword
})
})
.done(function(response) {
if (response.success) {
showSuccess('密码修改成功,请重新登录');
// 清空表单
$('#password-form')[0].reset();
// 可以选择跳转到登录页面
setTimeout(function() {
window.location.href = '/login';
}, 2000);
} else {
showError(response.message || '密码修改失败');
}
})
.fail(function() {
showError('密码修改失败,请重试');
});
}
// 加载使用统计
function loadUsageStats() {
$.get('/api/profile/stats')
.done(function(response) {
if (response.success) {
const data = response.data;
$('#total-prompts').text(data.total_prompts || 0);
$('#total-favorites').text(data.total_favorites || 0);
$('#days-active').text(data.days_active || 0);
// 加载最近活动
let activityHtml = '<div class="list-group">';
if (data.recent_activities && data.recent_activities.length > 0) {
data.recent_activities.forEach(activity => {
activityHtml += `
<div class="list-group-item">
<div class="d-flex justify-content-between">
<span>${activity.type}: ${activity.content}</span>
<small class="text-muted">${activity.time}</small>
</div>
</div>
`;
});
} else {
activityHtml += '<div class="list-group-item"><span class="text-muted">暂无活动记录</span></div>';
}
activityHtml += '</div>';
$('#recent-activity').html(activityHtml);
} else {
$('#recent-activity').html('<p class="text-danger">加载统计信息失败</p>');
}
})
.fail(function() {
$('#recent-activity').html('<p class="text-danger">加载统计信息失败</p>');
});
}
// 显示成功提示
function showSuccess(message) {
$('#success-message').text(message);
new bootstrap.Modal(document.getElementById('successModal')).show();
}
// 显示错误提示
function showError(message) {
$('#error-message').text(message);
new bootstrap.Modal(document.getElementById('errorModal')).show();
}
</script>
{% endblock %}

View File

@@ -258,6 +258,10 @@
<i class="fas fa-heart"></i>
我的收藏
</a>
<a href="{{ url_for('auth.profile_page') }}" class="nav-link">
<i class="fas fa-user"></i>
个人资料
</a>
<div class="user-menu" id="userMenu">
<!-- 用户菜单将通过JavaScript动态加载 -->
</div>