feat(auth): 增加管理员订单管理和个人充值记录功能
- 新增全员订单管理页面,仅限管理员访问 - 购买积分页新增显示用户个人充值记录,管理员可见全员记录 - 支付模块增加用户充值历史查看页面及对应 API 接口 - buy.html 模板新增充值记录展示,包括个人和管理员视角的切换标签 - base.html 添加网站图标图像替换原图标字体图标 - 更新支付宝配置 AppID 和应用私钥 - 增加页面脚本支持充值记录标签页切换功能 - 补充日志,记录更多用户登录成功信息
This commit is contained in:
parent
5dc2fbd0e7
commit
72bacacf4f
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -38,12 +38,32 @@ def notifications_page():
|
|||||||
"""系统通知管理页面"""
|
"""系统通知管理页面"""
|
||||||
return render_template('notifications.html')
|
return render_template('notifications.html')
|
||||||
|
|
||||||
|
@auth_bp.route('/admin/orders')
|
||||||
|
@admin_required
|
||||||
|
def admin_orders_page():
|
||||||
|
"""全员订单管理页面"""
|
||||||
|
return render_template('orders.html')
|
||||||
|
|
||||||
@auth_bp.route('/buy')
|
@auth_bp.route('/buy')
|
||||||
def buy_page():
|
def buy_page():
|
||||||
"""购买积分页面"""
|
"""购买积分页面"""
|
||||||
if 'user_id' not in session:
|
if 'user_id' not in session:
|
||||||
return redirect(url_for('auth.login_page'))
|
return redirect(url_for('auth.login_page'))
|
||||||
return render_template('buy.html')
|
|
||||||
|
from models import Order, User
|
||||||
|
user_id = session['user_id']
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
# 获取用户个人充值记录
|
||||||
|
personal_orders = Order.query.filter_by(user_id=user_id).order_by(Order.created_at.desc()).limit(10).all()
|
||||||
|
|
||||||
|
# 如果是管理员,获取全员记录
|
||||||
|
is_admin = user.has_permission('manage_system')
|
||||||
|
admin_orders = []
|
||||||
|
if is_admin:
|
||||||
|
admin_orders = Order.query.order_by(Order.created_at.desc()).limit(10).all()
|
||||||
|
|
||||||
|
return render_template('buy.html', personal_orders=personal_orders, admin_orders=admin_orders, is_admin=is_admin)
|
||||||
|
|
||||||
@auth_bp.route('/api/auth/send_code', methods=['POST'])
|
@auth_bp.route('/api/auth/send_code', methods=['POST'])
|
||||||
def send_code():
|
def send_code():
|
||||||
|
|||||||
@ -92,6 +92,38 @@ def payment_return():
|
|||||||
logger.error(f"处理同步回调时发生异常: {str(e)}", exc_info=True)
|
logger.error(f"处理同步回调时发生异常: {str(e)}", exc_info=True)
|
||||||
return f"处理支付回调失败: {str(e)}", 500
|
return f"处理支付回调失败: {str(e)}", 500
|
||||||
|
|
||||||
|
@payment_bp.route('/history', methods=['GET'])
|
||||||
|
def payment_history():
|
||||||
|
"""获取当前用户的充值历史记录"""
|
||||||
|
if 'user_id' not in session:
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
user_id = session['user_id']
|
||||||
|
orders = Order.query.filter_by(user_id=user_id).order_by(Order.created_at.desc()).all()
|
||||||
|
|
||||||
|
return render_template('recharge_history.html', orders=orders)
|
||||||
|
|
||||||
|
@payment_bp.route('/api/history', methods=['GET'])
|
||||||
|
def api_payment_history():
|
||||||
|
"""API 获取当前用户的充值历史记录"""
|
||||||
|
if 'user_id' not in session:
|
||||||
|
return jsonify({'code': 401, 'msg': '请先登录'}), 401
|
||||||
|
|
||||||
|
user_id = session['user_id']
|
||||||
|
orders = Order.query.filter_by(user_id=user_id).order_by(Order.created_at.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"orders": [{
|
||||||
|
"id": o.id,
|
||||||
|
"out_trade_no": o.out_trade_no,
|
||||||
|
"amount": float(o.amount),
|
||||||
|
"points": o.points,
|
||||||
|
"status": o.status,
|
||||||
|
"trade_no": o.trade_no,
|
||||||
|
"created_at": o.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"paid_at": o.paid_at.strftime('%Y-%m-%d %H:%M:%S') if o.paid_at else None
|
||||||
|
} for o in orders]
|
||||||
|
})
|
||||||
@payment_bp.route('/notify', methods=['POST'])
|
@payment_bp.route('/notify', methods=['POST'])
|
||||||
def payment_notify():
|
def payment_notify():
|
||||||
"""支付宝异步通知"""
|
"""支付宝异步通知"""
|
||||||
|
|||||||
@ -45,9 +45,9 @@ class Config:
|
|||||||
SMS_NEED_PARAM = False # 该模板需要参数,如使用系统赠送模板请改为 False
|
SMS_NEED_PARAM = False # 该模板需要参数,如使用系统赠送模板请改为 False
|
||||||
|
|
||||||
# 支付宝配置 (待填)
|
# 支付宝配置 (待填)
|
||||||
ALIPAY_APP_ID = "2021006126656681" # 支付宝 AppID
|
ALIPAY_APP_ID = "2021006125623511" # 支付宝 AppID
|
||||||
ALIPAY_APP_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
|
ALIPAY_APP_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
|
||||||
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
|
||||||
-----END RSA PRIVATE KEY-----""" # 应用私钥
|
-----END RSA PRIVATE KEY-----""" # 应用私钥
|
||||||
ALIPAY_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
ALIPAY_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlDx4KdOtOQE+tBq6jHKKFenRaRe2gbBnleBk++5gki9IQuxVyZUGTJixstf2gELFHWrGanpnwmGggXsqG+Rm5ZLJOlmFM1k0XeAIDvi6tP/rM+ZDFSu1bMBYtT5vzgVZC7mzIvOp9gsT/puqd3aNZmlviLD0R6OYN0zvFX+5qADZV7A9ziA+nXPFSHreBh7yY/q9ophVZNeHGPoYkDVI5++RrF1cALKOdit0giN5vxpe3ch9z3E6+FZg3LiP+1RW3tMiDQfp/SlVs6bNhLUtmlI5r7+mtFCKDUCEpnQ3S9e0II6rzyVXRyKCFs7qi5YzyhhmO3tJJoe9ilEFyNzfRQIDAQAB
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlDx4KdOtOQE+tBq6jHKKFenRaRe2gbBnleBk++5gki9IQuxVyZUGTJixstf2gELFHWrGanpnwmGggXsqG+Rm5ZLJOlmFM1k0XeAIDvi6tP/rM+ZDFSu1bMBYtT5vzgVZC7mzIvOp9gsT/puqd3aNZmlviLD0R6OYN0zvFX+5qADZV7A9ziA+nXPFSHreBh7yY/q9ophVZNeHGPoYkDVI5++RrF1cALKOdit0giN5vxpe3ch9z3E6+FZg3LiP+1RW3tMiDQfp/SlVs6bNhLUtmlI5r7+mtFCKDUCEpnQ3S9e0II6rzyVXRyKCFs7qi5YzyhhmO3tJJoe9ilEFyNzfRQIDAQAB
|
||||||
|
|||||||
@ -130,3 +130,7 @@
|
|||||||
[2026-01-13 23:59:02] INFO - 用户登录成功
|
[2026-01-13 23:59:02] INFO - 用户登录成功
|
||||||
[2026-01-14 16:45:23] INFO - 用户登录尝试
|
[2026-01-14 16:45:23] INFO - 用户登录尝试
|
||||||
[2026-01-14 16:45:23] INFO - 用户登录成功
|
[2026-01-14 16:45:23] INFO - 用户登录成功
|
||||||
|
[2026-01-14 20:12:31] INFO - 用户登录尝试
|
||||||
|
[2026-01-14 20:12:31] INFO - 用户登录成功
|
||||||
|
[2026-01-14 20:17:10] INFO - 用户登录尝试
|
||||||
|
[2026-01-14 20:17:10] INFO - 用户登录成功
|
||||||
|
|||||||
BIN
static/A_2IGfT6uTwlgAAAAAQmAAAAgAerF1AQ.png
Normal file
BIN
static/A_2IGfT6uTwlgAAAAAQmAAAAgAerF1AQ.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}AI 视界{% endblock %}</title>
|
<title>{% block title %}AI 视界{% endblock %}</title>
|
||||||
|
<link rel="icon" href="/static/A_2IGfT6uTwlgAAAAAQmAAAAgAerF1AQ.png" type="image/png">
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
<script src="{{ url_for('static', filename='js/lucide.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/lucide.min.js') }}"></script>
|
||||||
@ -18,8 +19,8 @@
|
|||||||
<div class="flex h-screen w-screen overflow-hidden">
|
<div class="flex h-screen w-screen overflow-hidden">
|
||||||
<!-- 全局系统菜单栏 (默认隐藏,仅在 initGlobalNav 成功后显示) -->
|
<!-- 全局系统菜单栏 (默认隐藏,仅在 initGlobalNav 成功后显示) -->
|
||||||
<nav id="globalNav" class="hidden w-20 flex-shrink-0 bg-slate-900 flex flex-col items-center py-8 z-40 shadow-2xl transition-all duration-500">
|
<nav id="globalNav" class="hidden w-20 flex-shrink-0 bg-slate-900 flex flex-col items-center py-8 z-40 shadow-2xl transition-all duration-500">
|
||||||
<div class="w-12 h-12 btn-primary logo-box rounded-2xl flex items-center justify-center mb-12 rotate-3">
|
<div class="w-12 h-12 mb-12 flex items-center justify-center p-1">
|
||||||
<i data-lucide="scan-eye" class="w-7 h-7"></i>
|
<img src="/static/A_2IGfT6uTwlgAAAAAQmAAAAgAerF1AQ.png" alt="Logo" class="w-full h-full object-contain rounded-xl shadow-lg">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="dynamicMenuList" class="flex-1 w-full px-2 space-y-4"></div>
|
<div id="dynamicMenuList" class="flex-1 w-full px-2 space-y-4"></div>
|
||||||
|
|||||||
@ -123,12 +123,161 @@
|
|||||||
支付安全保障 · 充值实时到账
|
支付安全保障 · 充值实时到账
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 充值记录区域 -->
|
||||||
|
<div class="space-y-8 pt-8 border-t border-slate-200">
|
||||||
|
<!-- 标签页切换 -->
|
||||||
|
<div class="flex items-center gap-4 bg-slate-100 p-1.5 rounded-2xl w-fit">
|
||||||
|
<button onclick="switchTab('personal')" id="tabBtn-personal" class="px-6 py-2.5 rounded-xl text-sm font-black transition-all bg-white text-slate-900 shadow-sm">
|
||||||
|
我的充值
|
||||||
|
</button>
|
||||||
|
{% if is_admin %}
|
||||||
|
<button onclick="switchTab('admin')" id="tabBtn-admin" class="px-6 py-2.5 rounded-xl text-sm font-black transition-all text-slate-500 hover:text-slate-700">
|
||||||
|
全员记录 (管理员)
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 我的充值列表 -->
|
||||||
|
<div id="tab-personal" class="bg-white/70 backdrop-blur-xl rounded-[2.5rem] border border-slate-200/50 shadow-2xl overflow-hidden animate-in fade-in duration-500">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-slate-100 bg-slate-50/50">
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">订单号</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">积分/金额</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">状态</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">支付时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% if personal_orders %}
|
||||||
|
{% for order in personal_orders %}
|
||||||
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xs font-bold text-slate-700">{{ order.out_trade_no }}</span>
|
||||||
|
<span class="text-[10px] text-slate-400 font-mono">Ali: {{ order.trade_no or '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="zap" class="w-3 h-3 text-amber-500"></i>
|
||||||
|
<span class="text-sm font-black text-slate-900">+{{ order.points }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[10px] font-bold text-slate-400">¥{{ order.amount }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
{% if order.status == 'PAID' %}
|
||||||
|
<span class="px-2 py-1 bg-emerald-50 text-emerald-600 text-[10px] font-black rounded-lg border border-emerald-100">已完成</span>
|
||||||
|
{% elif order.status == 'PENDING' %}
|
||||||
|
<span class="px-2 py-1 bg-amber-50 text-amber-600 text-[10px] font-black rounded-lg border border-amber-100">待支付</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-black rounded-lg border border-slate-100">已取消</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<span class="text-[10px] font-bold text-slate-400">{{ order.paid_at.strftime('%m-%d %H:%M') if order.paid_at else '-' }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="px-8 py-16 text-center">
|
||||||
|
<p class="text-slate-300 font-bold text-sm">暂无记录</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 bg-slate-50/50 border-t border-slate-100 text-center">
|
||||||
|
<a href="/payment/history" class="text-[10px] font-black text-indigo-600 hover:text-indigo-700 uppercase tracking-widest flex items-center justify-center gap-1">
|
||||||
|
查看更多记录 <i data-lucide="chevron-right" class="w-3 h-3"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if is_admin %}
|
||||||
|
<!-- 管理员全员记录列表 -->
|
||||||
|
<div id="tab-admin" class="hidden bg-white/70 backdrop-blur-xl rounded-[2.5rem] border border-slate-200/50 shadow-2xl overflow-hidden animate-in fade-in duration-500">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-slate-100 bg-slate-50/50">
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">用户信息</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">订单号</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">积分/金额</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% if admin_orders %}
|
||||||
|
{% for order in admin_orders %}
|
||||||
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xs font-bold text-slate-700">{{ order.user.phone if order.user else '未知' }}</span>
|
||||||
|
<span class="text-[10px] text-slate-400">UID: {{ order.user_id }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[10px] font-bold text-slate-500">{{ order.out_trade_no }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xs font-black text-slate-900">+{{ order.points }}</span>
|
||||||
|
<span class="text-[10px] font-bold text-slate-400">¥{{ order.amount }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
{% if order.status == 'PAID' %}
|
||||||
|
<span class="px-2 py-1 bg-emerald-50 text-emerald-600 text-[10px] font-black rounded-lg border border-emerald-100">已完成</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="px-2 py-1 bg-slate-50 text-slate-400 text-[10px] font-black rounded-lg border border-slate-100">{{ order.status }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 bg-slate-50/50 border-t border-slate-100 text-center">
|
||||||
|
<a href="/admin/orders" class="text-[10px] font-black text-indigo-600 hover:text-indigo-700 uppercase tracking-widest flex items-center justify-center gap-1">
|
||||||
|
进入后台管理全员订单 <i data-lucide="chevron-right" class="w-3 h-3"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
// 页面加载后的逻辑
|
function switchTab(tab) {
|
||||||
|
// 切换内容显隐
|
||||||
|
document.getElementById('tab-personal').classList.toggle('hidden', tab !== 'personal');
|
||||||
|
const adminTab = document.getElementById('tab-admin');
|
||||||
|
if(adminTab) adminTab.classList.toggle('hidden', tab !== 'admin');
|
||||||
|
|
||||||
|
// 切换按钮样式
|
||||||
|
const personalBtn = document.getElementById('tabBtn-personal');
|
||||||
|
const adminBtn = document.getElementById('tabBtn-admin');
|
||||||
|
|
||||||
|
if(tab === 'personal') {
|
||||||
|
personalBtn.className = "px-6 py-2.5 rounded-xl text-sm font-black transition-all bg-white text-slate-900 shadow-sm";
|
||||||
|
if(adminBtn) adminBtn.className = "px-6 py-2.5 rounded-xl text-sm font-black transition-all text-slate-500 hover:text-slate-700";
|
||||||
|
} else {
|
||||||
|
personalBtn.className = "px-6 py-2.5 rounded-xl text-sm font-black transition-all text-slate-500 hover:text-slate-700";
|
||||||
|
if(adminBtn) adminBtn.className = "px-6 py-2.5 rounded-xl text-sm font-black transition-all bg-white text-slate-900 shadow-sm";
|
||||||
|
}
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
148
templates/orders.html
Normal file
148
templates/orders.html
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}全员充值记录 - 管理后台{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex-1 overflow-y-auto p-8 relative">
|
||||||
|
<div class="max-w-7xl mx-auto space-y-8">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<h1 class="text-3xl font-black text-slate-900 tracking-tight">全员充值记录</h1>
|
||||||
|
<p class="text-slate-500 font-bold text-sm flex items-center gap-2">
|
||||||
|
<i data-lucide="shield-check" class="w-4 h-4"></i>
|
||||||
|
管理和查询系统内所有用户的充值情况
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="relative">
|
||||||
|
<input type="text" id="orderSearch" placeholder="搜索手机号/订单号..." class="pl-10 pr-4 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-indigo-500 outline-none transition-all w-64">
|
||||||
|
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||||
|
</div>
|
||||||
|
<button onclick="loadOrders()" class="w-10 h-10 bg-white border border-slate-200 rounded-xl flex items-center justify-center text-slate-500 hover:text-indigo-600 transition-colors">
|
||||||
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记录列表 -->
|
||||||
|
<div class="bg-white/70 backdrop-blur-xl rounded-[2.5rem] border border-slate-200/50 shadow-2xl overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-slate-100 bg-slate-50/50">
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">用户信息</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">订单详情</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">积分/金额</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">状态</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="orderList" class="divide-y divide-slate-100">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="px-8 py-20 text-center">
|
||||||
|
<div class="flex flex-col items-center gap-4 animate-pulse">
|
||||||
|
<i data-lucide="loader-2" class="w-8 h-8 text-indigo-500 animate-spin"></i>
|
||||||
|
<p class="text-slate-400 font-bold">正在获取记录...</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
let allOrders = [];
|
||||||
|
|
||||||
|
async function loadOrders() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/admin/orders');
|
||||||
|
const d = await r.json();
|
||||||
|
allOrders = d.orders || [];
|
||||||
|
renderOrders(allOrders);
|
||||||
|
} catch (e) {
|
||||||
|
showToast('加载失败: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOrders(orders) {
|
||||||
|
const list = document.getElementById('orderList');
|
||||||
|
if (orders.length === 0) {
|
||||||
|
list.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="px-8 py-20 text-center">
|
||||||
|
<div class="flex flex-col items-center gap-4 opacity-20">
|
||||||
|
<i data-lucide="inbox" class="w-16 h-16"></i>
|
||||||
|
<p class="font-black text-xl">暂无记录</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
lucide.createIcons();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = orders.map(o => `
|
||||||
|
<tr class="hover:bg-slate-50/50 transition-colors group">
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 bg-indigo-50 text-indigo-600 rounded-xl flex items-center justify-center font-black text-xs">
|
||||||
|
${o.user_phone.slice(-4)}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-sm font-bold text-slate-700">${o.user_phone}</span>
|
||||||
|
<span class="text-[10px] text-slate-400">UID: ${o.id}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xs font-bold text-slate-600">${o.out_trade_no}</span>
|
||||||
|
<span class="text-[10px] text-slate-400 font-mono">Ali: ${o.trade_no || '-'}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="zap" class="w-3 h-3 text-amber-500"></i>
|
||||||
|
<span class="text-sm font-black text-slate-900">+${o.points}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold text-slate-400">¥${o.amount}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
${o.status === 'PAID'
|
||||||
|
? '<span class="px-3 py-1 bg-emerald-50 text-emerald-600 text-[10px] font-black rounded-lg border border-emerald-100">已完成</span>'
|
||||||
|
: o.status === 'PENDING'
|
||||||
|
? '<span class="px-3 py-1 bg-amber-50 text-amber-600 text-[10px] font-black rounded-lg border border-amber-100">待支付</span>'
|
||||||
|
: '<span class="px-3 py-1 bg-slate-50 text-slate-400 text-[10px] font-black rounded-lg border border-slate-100">已取消</span>'
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[10px] font-bold text-slate-500">创建: ${o.created_at}</span>
|
||||||
|
<span class="text-[10px] font-bold text-emerald-500">${o.paid_at ? '完成: ' + o.paid_at : ''}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('orderSearch').oninput = (e) => {
|
||||||
|
const val = e.target.value.toLowerCase();
|
||||||
|
const filtered = allOrders.filter(o =>
|
||||||
|
o.user_phone.includes(val) ||
|
||||||
|
o.out_trade_no.toLowerCase().includes(val)
|
||||||
|
);
|
||||||
|
renderOrders(filtered);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadOrders();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
87
templates/recharge_history.html
Normal file
87
templates/recharge_history.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}充值记录 - AI 视界{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex-1 overflow-y-auto p-8 relative">
|
||||||
|
<div class="max-w-5xl mx-auto space-y-8">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<h1 class="text-3xl font-black text-slate-900 tracking-tight">充值记录</h1>
|
||||||
|
<p class="text-slate-500 font-bold text-sm flex items-center gap-2">
|
||||||
|
<i data-lucide="history" class="w-4 h-4"></i>
|
||||||
|
查看您的所有积分充值历史
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ url_for('auth.buy_page') }}" class="btn-primary px-6 py-3 rounded-2xl flex items-center gap-2 shadow-xl shadow-indigo-200">
|
||||||
|
<i data-lucide="plus-circle" class="w-5 h-5"></i>
|
||||||
|
立即充值
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记录列表 -->
|
||||||
|
<div class="bg-white/70 backdrop-blur-xl rounded-[2.5rem] border border-slate-200/50 shadow-2xl overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-slate-100 bg-slate-50/50">
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">订单号</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">积分</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">金额</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">状态</th>
|
||||||
|
<th class="px-8 py-5 text-[10px] font-black text-slate-400 uppercase tracking-widest">支付时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% if orders %}
|
||||||
|
{% for order in orders %}
|
||||||
|
<tr class="hover:bg-slate-50/50 transition-colors group">
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-sm font-bold text-slate-700">{{ order.out_trade_no }}</span>
|
||||||
|
<span class="text-[10px] text-slate-400 font-mono">Ali: {{ order.trade_no or '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-amber-50 text-amber-500 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="zap" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-black text-slate-900">+{{ order.points }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<span class="text-sm font-bold text-slate-600">¥{{ order.amount }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
{% if order.status == 'PAID' %}
|
||||||
|
<span class="px-3 py-1 bg-emerald-50 text-emerald-600 text-[10px] font-black rounded-lg border border-emerald-100">已完成</span>
|
||||||
|
{% elif order.status == 'PENDING' %}
|
||||||
|
<span class="px-3 py-1 bg-amber-50 text-amber-600 text-[10px] font-black rounded-lg border border-amber-100">待支付</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="px-3 py-1 bg-slate-50 text-slate-400 text-[10px] font-black rounded-lg border border-slate-100">已取消</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-8 py-5">
|
||||||
|
<span class="text-xs font-bold text-slate-400">{{ order.paid_at.strftime('%Y-%m-%d %H:%M') if order.paid_at else '-' }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="px-8 py-20 text-center">
|
||||||
|
<div class="flex flex-col items-center gap-4 opacity-20">
|
||||||
|
<i data-lucide="inbox" class="w-16 h-16"></i>
|
||||||
|
<p class="font-black text-xl">暂无充值记录</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user