From 22a109d6c07d205f62a97ff21f22c2af792b8a86 Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Mon, 27 Apr 2026 20:02:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=A4=B4=E5=83=8F?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 76 +++++++++++++++++++++++++++- www/app.js | 133 ++++++++++++++++++++++++++++++++++++++++++++++--- www/style.css | 25 ++++++++++ 3 files changed, 227 insertions(+), 7 deletions(-) diff --git a/backend/app.py b/backend/app.py index b3afae0..f6c690f 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ AI Chat App - 后台管理服务 -端口: 19020 (与前端同一端口) +端口: 19021 (与前端同一端口) """ from flask import Flask, jsonify, request, send_from_directory @@ -11,6 +11,7 @@ import json import sqlite3 from datetime import datetime import hashlib +import base64 app = Flask(__name__, static_folder='../www') CORS(app) @@ -18,6 +19,11 @@ CORS(app) # 数据库路径 DB_PATH = os.path.join(os.path.dirname(__file__), 'data.db') +# 头像存储目录 +AVATAR_DIR = os.path.join(os.path.dirname(__file__), 'avatars') +if not os.path.exists(AVATAR_DIR): + os.makedirs(AVATAR_DIR) + # 管理员账户(默认) ADMIN_USERNAME = 'admin' ADMIN_PASSWORD_HASH = hashlib.sha256('admin123'.encode()).hexdigest() @@ -610,6 +616,74 @@ def change_user_password(id): return jsonify({'success': True}) +# ==================== 用户头像上传 ==================== + +@app.route('/api/user//avatar', methods=['POST']) +def upload_user_avatar(user_id): + """上传用户头像""" + data = request.json + avatar_data = data.get('avatar') # base64 格式图片 + + if not avatar_data: + return jsonify({'error': '头像数据不能为空'}), 400 + + # 解析 base64 数据 + try: + # 支持两种格式: + # 1. data:image/png;base64,xxxxx + # 2. 纯 base64 字符串 + + if avatar_data.startswith('data:'): + # 提取格式和内容 + header, content = avatar_data.split(',', 1) + # 提取图片格式 + mime_type = header.split(':')[1].split(';')[0] + ext = mime_type.split('/')[1] if '/' in mime_type else 'png' + else: + content = avatar_data + ext = 'png' + + # 解码 base64 + image_bytes = base64.b64decode(content) + + # 验证图片大小(最大 2MB) + if len(image_bytes) > 2 * 1024 * 1024: + return jsonify({'error': '头像大小不能超过2MB'}), 400 + + # 生成文件名 + filename = f'user_{user_id}_{int(datetime.now().timestamp())}.{ext}' + filepath = os.path.join(AVATAR_DIR, filename) + + # 保存文件 + with open(filepath, 'wb') as f: + f.write(image_bytes) + + # 更新数据库(存储文件名) + conn = get_db() + cursor = conn.cursor() + cursor.execute('UPDATE users SET avatar=?, updated_at=CURRENT_TIMESTAMP WHERE id=?', + (filename, user_id)) + conn.commit() + conn.close() + + return jsonify({'success': True, 'avatar': filename, 'avatar_url': f'/api/avatars/{filename}'}) + + except Exception as e: + return jsonify({'error': f'头像上传失败: {str(e)}'}), 500 + + +@app.route('/api/avatars/', methods=['GET']) +def get_avatar(filename): + """获取头像图片""" + return send_from_directory(AVATAR_DIR, filename) + + +@app.route('/api/admin/users//avatar', methods=['POST']) +def admin_upload_user_avatar(user_id): + """管理员上传用户头像""" + return upload_user_avatar(user_id) + + # ==================== 用户对话数据同步 ==================== @app.route('/api/user//conversations', methods=['GET']) diff --git a/www/app.js b/www/app.js index 38a7b40..95663b4 100644 --- a/www/app.js +++ b/www/app.js @@ -1493,6 +1493,9 @@ function renderProfilePage() { const userAvatar = currentUser?.avatar || '👤'; const userSignature = currentUser?.signature || '这个人很懒,什么都没写~'; + // 渲染头像(支持 emoji 和上传图片) + const avatarHtml = renderAvatar(userAvatar); + return `