update pay ui
This commit is contained in:
167
app_vx.py
167
app_vx.py
@@ -1433,6 +1433,25 @@ class Comment(db.Model):
|
|||||||
replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id]))
|
replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id]))
|
||||||
|
|
||||||
|
|
||||||
|
class Feedback(db.Model):
|
||||||
|
"""用户反馈"""
|
||||||
|
__tablename__ = 'user_feedback'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # 可匿名反馈
|
||||||
|
feedback_type = db.Column(db.String(50), default='general') # 反馈类型: general/bug/suggestion/complaint
|
||||||
|
title = db.Column(db.String(200)) # 反馈标题
|
||||||
|
content = db.Column(db.Text, nullable=False) # 反馈内容
|
||||||
|
contact = db.Column(db.String(100)) # 联系方式(可选)
|
||||||
|
images = db.Column(db.JSON) # 附带图片URL列表
|
||||||
|
status = db.Column(db.String(20), default='pending') # pending/processing/resolved/closed
|
||||||
|
admin_reply = db.Column(db.Text) # 管理员回复
|
||||||
|
created_at = db.Column(db.DateTime, default=beijing_now)
|
||||||
|
updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now)
|
||||||
|
|
||||||
|
user = db.relationship('User', backref='feedbacks')
|
||||||
|
|
||||||
|
|
||||||
class StockBasicInfo(db.Model):
|
class StockBasicInfo(db.Model):
|
||||||
__tablename__ = 'ea_stocklist'
|
__tablename__ = 'ea_stocklist'
|
||||||
|
|
||||||
@@ -4300,7 +4319,19 @@ def api_bindphone_wechat():
|
|||||||
'data': None
|
'data': None
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 4. 检查手机号是否已被其他用户绑定
|
# 4. 检查当前用户是否已绑定此手机号
|
||||||
|
if user.phone == phone_number and user.phone_confirmed:
|
||||||
|
# 已经绑定过了,直接返回成功
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'message': '手机号已绑定',
|
||||||
|
'data': {
|
||||||
|
'phone': phone_number,
|
||||||
|
'bindcd': True
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5. 检查手机号是否已被其他用户绑定(只查确认绑定的)
|
||||||
existing_user = User.query.filter(
|
existing_user = User.query.filter(
|
||||||
User.phone == phone_number,
|
User.phone == phone_number,
|
||||||
User.phone_confirmed == True,
|
User.phone_confirmed == True,
|
||||||
@@ -4308,13 +4339,14 @@ def api_bindphone_wechat():
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
if existing_user:
|
if existing_user:
|
||||||
|
logger.warning(f"手机号 {phone_number[:3]}****{phone_number[-4:]} 已被用户 {existing_user.id} 绑定,当前用户 {user.id} 尝试绑定失败")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'code': 400,
|
'code': 400,
|
||||||
'message': '该手机号已被其他账号绑定',
|
'message': '该手机号已被其他账号绑定',
|
||||||
'data': None
|
'data': None
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 5. 更新用户手机号
|
# 6. 更新用户手机号
|
||||||
user.phone = phone_number
|
user.phone = phone_number
|
||||||
user.phone_confirmed = True
|
user.phone_confirmed = True
|
||||||
user.phone_confirm_time = beijing_now()
|
user.phone_confirm_time = beijing_now()
|
||||||
@@ -5752,11 +5784,15 @@ def parse_best_matches(best_matches_value):
|
|||||||
for item in data:
|
for item in data:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
# 新结构:包含研报信息的字典
|
# 新结构:包含研报信息的字典
|
||||||
|
# 将相关度转为整数百分比 (0.928 -> 93)
|
||||||
|
raw_score = item.get('best_report_match_ratio', 0)
|
||||||
|
int_score = int(round(raw_score * 100)) if raw_score and raw_score <= 1 else int(round(raw_score)) if raw_score else 0
|
||||||
|
|
||||||
stock_info = {
|
stock_info = {
|
||||||
'code': item.get('stock_code', ''),
|
'code': item.get('stock_code', ''),
|
||||||
'name': item.get('company_name', ''),
|
'name': item.get('company_name', ''),
|
||||||
'description': item.get('original_description', ''),
|
'description': item.get('original_description', ''),
|
||||||
'score': item.get('best_report_match_ratio', 0),
|
'score': int_score,
|
||||||
# 研报引用信息
|
# 研报引用信息
|
||||||
'report': {
|
'report': {
|
||||||
'title': item.get('best_report_title', ''),
|
'title': item.get('best_report_title', ''),
|
||||||
@@ -6010,11 +6046,14 @@ def api_calendar_events():
|
|||||||
if stock_data:
|
if stock_data:
|
||||||
for stock_info in stock_data:
|
for stock_info in stock_data:
|
||||||
if isinstance(stock_info, list) and len(stock_info) >= 2:
|
if isinstance(stock_info, list) and len(stock_info) >= 2:
|
||||||
|
# 将相关度转为整数百分比
|
||||||
|
raw_score = stock_info[3] if len(stock_info) > 3 else 0
|
||||||
|
int_score = int(round(raw_score * 100)) if raw_score and raw_score <= 1 else int(round(raw_score)) if raw_score else 0
|
||||||
parsed_stocks.append({
|
parsed_stocks.append({
|
||||||
'code': stock_info[0],
|
'code': stock_info[0],
|
||||||
'name': stock_info[1],
|
'name': stock_info[1],
|
||||||
'description': stock_info[2] if len(stock_info) > 2 else '',
|
'description': stock_info[2] if len(stock_info) > 2 else '',
|
||||||
'score': stock_info[3] if len(stock_info) > 3 else 0,
|
'score': int_score,
|
||||||
'report': None
|
'report': None
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -6741,12 +6780,16 @@ def api_user_profile():
|
|||||||
|
|
||||||
# 总评论数(发出的评论 + 收到的评论和回复)
|
# 总评论数(发出的评论 + 收到的评论和回复)
|
||||||
total_comments = comments_made + comments_received + replies_received
|
total_comments = comments_made + comments_received + replies_received
|
||||||
|
# 判断手机号绑定状态
|
||||||
|
phone_bindcd = bool(user.phone and user.phone_confirmed)
|
||||||
|
|
||||||
profile_data = {
|
profile_data = {
|
||||||
'basic_info': {
|
'basic_info': {
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
'username': user.username,
|
'username': user.username,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'phone': user.phone,
|
'phone': user.phone if phone_bindcd else None,
|
||||||
|
'phone_bindcd': phone_bindcd,
|
||||||
'nickname': user.nickname,
|
'nickname': user.nickname,
|
||||||
'avatar_url': get_full_avatar_url(user.avatar_url), # 修改这里
|
'avatar_url': get_full_avatar_url(user.avatar_url), # 修改这里
|
||||||
'bio': user.bio,
|
'bio': user.bio,
|
||||||
@@ -6809,6 +6852,116 @@ def api_user_profile():
|
|||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/feedback', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def api_user_feedback():
|
||||||
|
"""用户反馈接口"""
|
||||||
|
try:
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# 获取请求数据(兼容多种格式)
|
||||||
|
data = request.get_json(force=True, silent=True)
|
||||||
|
if not data:
|
||||||
|
data = request.form.to_dict()
|
||||||
|
|
||||||
|
content = data.get('content', '').strip()
|
||||||
|
if not content:
|
||||||
|
return jsonify({
|
||||||
|
'code': 400,
|
||||||
|
'message': '反馈内容不能为空',
|
||||||
|
'data': None
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
feedback_type = data.get('type', 'general')
|
||||||
|
title = data.get('title', '').strip()
|
||||||
|
contact = data.get('contact', '').strip()
|
||||||
|
images = data.get('images', [])
|
||||||
|
|
||||||
|
# 确保images是列表
|
||||||
|
if isinstance(images, str):
|
||||||
|
images = [images] if images else []
|
||||||
|
|
||||||
|
# 创建反馈记录
|
||||||
|
feedback = Feedback(
|
||||||
|
user_id=user.id,
|
||||||
|
feedback_type=feedback_type,
|
||||||
|
title=title if title else None,
|
||||||
|
content=content,
|
||||||
|
contact=contact if contact else user.phone or user.email,
|
||||||
|
images=images if images else None,
|
||||||
|
status='pending'
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(feedback)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'message': '反馈提交成功,感谢您的宝贵意见!',
|
||||||
|
'data': {
|
||||||
|
'feedback_id': feedback.id,
|
||||||
|
'status': feedback.status,
|
||||||
|
'created_at': feedback.created_at.isoformat() if feedback.created_at else None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({
|
||||||
|
'code': 500,
|
||||||
|
'message': f'反馈提交失败: {str(e)}',
|
||||||
|
'data': None
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/feedback', methods=['GET'])
|
||||||
|
@token_required
|
||||||
|
def api_user_feedback_list():
|
||||||
|
"""获取用户反馈列表"""
|
||||||
|
try:
|
||||||
|
user = request.user
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
per_page = request.args.get('per_page', 10, type=int)
|
||||||
|
|
||||||
|
feedbacks = Feedback.query.filter_by(user_id=user.id) \
|
||||||
|
.order_by(Feedback.created_at.desc()) \
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
|
feedback_list = []
|
||||||
|
for fb in feedbacks.items:
|
||||||
|
feedback_list.append({
|
||||||
|
'id': fb.id,
|
||||||
|
'type': fb.feedback_type,
|
||||||
|
'title': fb.title,
|
||||||
|
'content': fb.content,
|
||||||
|
'status': fb.status,
|
||||||
|
'admin_reply': fb.admin_reply,
|
||||||
|
'created_at': fb.created_at.isoformat() if fb.created_at else None,
|
||||||
|
'updated_at': fb.updated_at.isoformat() if fb.updated_at else None
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'message': 'success',
|
||||||
|
'data': {
|
||||||
|
'feedbacks': feedback_list,
|
||||||
|
'pagination': {
|
||||||
|
'page': page,
|
||||||
|
'per_page': per_page,
|
||||||
|
'total': feedbacks.total,
|
||||||
|
'pages': feedbacks.pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'code': 500,
|
||||||
|
'message': str(e),
|
||||||
|
'data': None
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
# 在文件开头添加缓存变量
|
# 在文件开头添加缓存变量
|
||||||
_agreements_cache = {}
|
_agreements_cache = {}
|
||||||
_cache_loaded = False
|
_cache_loaded = False
|
||||||
@@ -7262,8 +7415,8 @@ if __name__ == '__main__':
|
|||||||
# SSL 配置
|
# SSL 配置
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
if not args.no_ssl:
|
if not args.no_ssl:
|
||||||
cert_file = '/etc/letsencrypt/live/api.valuefrontier.cn/fullchain.pem'
|
cert_file = '/etc/nginx/ssl/api.valuefrontier.cn/fullchain.pem'
|
||||||
key_file = '/etc/letsencrypt/live/api.valuefrontier.cn/privkey.pem'
|
key_file = '/etc/nginx/ssl/api.valuefrontier.cn/privkey.pem'
|
||||||
if os.path.exists(cert_file) and os.path.exists(key_file):
|
if os.path.exists(cert_file) and os.path.exists(key_file):
|
||||||
ssl_context = (cert_file, key_file)
|
ssl_context = (cert_file, key_file)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user