新增个人资料功能
This commit is contained in:
Binary file not shown.
@@ -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)}'
|
||||
})
|
||||
|
||||
408
src/flask_prompt_master/templates/auth/profile.html
Normal file
408
src/flask_prompt_master/templates/auth/profile.html
Normal 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 %}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user