Browse Source

单网页

Taio_O 4 tuần trước cách đây
mục cha
commit
8dcb68013c
4 tập tin đã thay đổi với 605 bổ sung1 xóa
  1. 35 0
      admin/app.js
  2. 21 1
      admin/index.js
  3. 131 0
      admin/routes/public.js
  4. 418 0
      admin/views/statistics_bill.html

+ 35 - 0
admin/app.js

@@ -0,0 +1,35 @@
+const express = require('express');
+const cors = require('cors');
+const mongoose = require('mongoose');
+const path = require('path');
+
+// 导入路由
+const publicRoutes = require('./routes/public');
+
+// 创建 Express 应用
+const app = express();
+
+// 中间件
+app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+// 静态文件
+app.use(express.static(path.join(__dirname, 'views')));
+
+// 注册公共路由(不需要认证)
+app.use('/api/public', publicRoutes);
+
+// 错误处理中间件
+app.use((err, req, res, next) => {
+    console.error(err.stack);
+    res.status(500).json({ message: '服务器错误' });
+});
+
+// 启动服务器
+const PORT = process.env.PORT || 3000;
+app.listen(PORT, () => {
+    console.log(`服务器运行在端口 ${PORT}`);
+});
+
+module.exports = app; 

+ 21 - 1
admin/index.js

@@ -48,6 +48,7 @@ const bot = new TelegramBot(process.env.BOT_TOKEN, {
 // 中间件
 app.use(cors());
 app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
 app.use('/admin/views', express.static(path.join(__dirname, 'views')));
 
 // 路由
@@ -943,4 +944,23 @@ bot.on('my_chat_member', async (msg) => {
             详情: error.message
         }));
     }
-});
+});
+
+// 导入公共路由
+const publicRoutes = require('./routes/public');
+
+// 注册公共路由
+app.use('/api/public', publicRoutes);
+
+// 错误处理中间件
+app.use((err, req, res, next) => {
+    console.error(err.stack);
+    res.status(500).json({ message: '服务器错误' });
+});
+
+// 404 处理
+app.use((req, res) => {
+    res.status(404).json({ message: '未找到请求的资源' });
+});
+
+module.exports = app;

+ 131 - 0
admin/routes/public.js

@@ -0,0 +1,131 @@
+const express = require('express');
+const router = express.Router();
+const Transaction = require('../models/Transaction');
+
+// 获取群组交易记录(不需要认证)
+router.get('/transactions', async (req, res) => {
+    try {
+        const { page = 1, limit = 10, groupId } = req.query;
+
+        if (!groupId) {
+            return res.status(400).json({ message: '缺少群组ID参数' });
+        }
+
+        console.log('查询参数:', { page, limit, groupId });
+
+        // 获取交易记录
+        const result = await Transaction.findAll(
+            { groupId: groupId.toString() },
+            parseInt(page),
+            parseInt(limit)
+        );
+
+        console.log('查询结果:', result);
+
+        // 获取统计数据
+        const today = new Date();
+        today.setHours(0, 0, 0, 0);
+        const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
+
+        // 获取今日收入
+        const todayResult = await Transaction.findAll(
+            { 
+                groupId: groupId.toString(),
+                startDate: today.toISOString().split('T')[0],
+                type: 'deposit'
+            },
+            1,
+            1000000
+        );
+        const todayIncome = todayResult.transactions.reduce((sum, t) => sum + parseFloat(t.amount), 0);
+
+        // 获取本月收入
+        const monthResult = await Transaction.findAll(
+            { 
+                groupId: groupId.toString(),
+                startDate: firstDayOfMonth.toISOString().split('T')[0],
+                type: 'deposit'
+            },
+            1,
+            1000000
+        );
+        const monthIncome = monthResult.transactions.reduce((sum, t) => sum + parseFloat(t.amount), 0);
+
+        // 获取总收入
+        const totalResult = await Transaction.findAll(
+            { 
+                groupId: groupId.toString(),
+                type: 'deposit'
+            },
+            1,
+            1000000
+        );
+        const totalIncome = totalResult.transactions.reduce((sum, t) => sum + parseFloat(t.amount), 0);
+
+        // 获取今日订单数
+        const todayOrdersResult = await Transaction.findAll(
+            { 
+                groupId: groupId.toString(),
+                startDate: today.toISOString().split('T')[0]
+            },
+            1,
+            1000000
+        );
+        const todayOrders = todayOrdersResult.transactions.length;
+
+        // 计算总结数据
+        const depositCount = totalResult.transactions.length;
+        const withdrawResult = await Transaction.findAll(
+            { 
+                groupId: groupId.toString(),
+                type: 'withdrawal'
+            },
+            1,
+            1000000
+        );
+        const withdrawCount = withdrawResult.transactions.length;
+        const totalWithdraw = withdrawResult.transactions.reduce((sum, t) => sum + parseFloat(t.amount), 0);
+
+        // 计算费率(示例:入款费率1%,下发费率0.5%)
+        const depositFeeRate = 1;
+        const withdrawFeeRate = 0.5;
+        const singleWithdrawFee = 1; // 单笔附加费1元
+
+        // 计算应下发金额(入款总额 * (1 - 入款费率))
+        const shouldWithdraw = totalIncome * (1 - depositFeeRate / 100);
+
+        // 计算下发附加费总额
+        const totalWithdrawFee = withdrawCount * singleWithdrawFee;
+
+        // 计算余额(应下发 - 总下发 - 下发附加费总额)
+        const balance = shouldWithdraw - totalWithdraw - totalWithdrawFee;
+
+        res.json({
+            transactions: result.transactions,
+            total: result.total,
+            statistics: {
+                todayIncome,
+                monthIncome,
+                totalIncome,
+                todayOrders
+            },
+            summary: {
+                totalDeposit: totalIncome,
+                depositCount,
+                depositFeeRate,
+                shouldWithdraw,
+                totalWithdraw,
+                withdrawCount,
+                withdrawFeeRate,
+                singleWithdrawFee,
+                totalWithdrawFee,
+                balance
+            }
+        });
+    } catch (error) {
+        console.error('获取群组交易记录失败:', error);
+        res.status(500).json({ message: '获取群组交易记录失败' });
+    }
+});
+
+module.exports = router; 

+ 418 - 0
admin/views/statistics_bill.html

@@ -0,0 +1,418 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>机器人统计账单</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
+    <style>
+        body {
+            background-color: #f5f5f5;
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+        }
+        .container {
+            max-width: 1200px;
+            margin: 20px auto;
+            padding: 20px;
+        }
+        .card {
+            background: #fff;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+        }
+        .card-header {
+            background: #fff;
+            border-bottom: 1px solid #eee;
+            padding: 15px 20px;
+            font-weight: 600;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+        .stat-card {
+            background: linear-gradient(45deg, #2196F3, #1976D2);
+            color: white;
+            border-radius: 8px;
+            padding: 20px;
+            margin-bottom: 20px;
+            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+        .stat-card .title {
+            font-size: 14px;
+            opacity: 0.8;
+        }
+        .stat-card .value {
+            font-size: 24px;
+            font-weight: bold;
+            margin: 10px 0;
+        }
+        .table {
+            margin-bottom: 0;
+        }
+        .table th {
+            background: #f8f9fa;
+            font-weight: 600;
+        }
+        .badge {
+            padding: 5px 10px;
+            border-radius: 4px;
+        }
+        .badge-success {
+            background-color: #28a745;
+        }
+        .badge-danger {
+            background-color: #dc3545;
+        }
+        .pagination {
+            margin: 20px 0;
+        }
+        .pagination .page-link {
+            color: #2196F3;
+        }
+        .pagination .page-item.active .page-link {
+            background-color: #2196F3;
+            border-color: #2196F3;
+        }
+        .summary-table {
+            width: 100%;
+            border-collapse: collapse;
+            margin-top: 20px;
+        }
+        .summary-table th,
+        .summary-table td {
+            padding: 12px;
+            text-align: left;
+            border-bottom: 1px solid #eee;
+        }
+        .summary-table th {
+            background-color: #f8f9fa;
+            font-weight: 600;
+        }
+        .summary-table tr:last-child td {
+            border-bottom: none;
+            font-weight: bold;
+        }
+        .nav-tabs {
+            border-bottom: 1px solid #dee2e6;
+            margin-bottom: 20px;
+        }
+        .nav-tabs .nav-link {
+            border: none;
+            color: #6c757d;
+            padding: 10px 20px;
+            font-weight: 500;
+        }
+        .nav-tabs .nav-link.active {
+            color: #2196F3;
+            border-bottom: 2px solid #2196F3;
+            background: none;
+        }
+        .tab-content {
+            padding: 20px 0;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="row">
+            <div class="col-md-3">
+                <div class="stat-card">
+                    <div class="title">今日收入</div>
+                    <div class="value" id="todayIncome">¥0.00</div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="stat-card">
+                    <div class="title">本月收入</div>
+                    <div class="value" id="monthlyIncome">¥0.00</div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="stat-card">
+                    <div class="title">总收入</div>
+                    <div class="value" id="totalIncome">¥0.00</div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="stat-card">
+                    <div class="title">今日订单数</div>
+                    <div class="value" id="todayOrders">0</div>
+                </div>
+            </div>
+        </div>
+
+        <div class="card">
+            <div class="card-header">
+                <span>交易记录</span>
+                <ul class="nav nav-tabs card-header-tabs">
+                    <li class="nav-item">
+                        <a class="nav-link active" data-bs-toggle="tab" href="#deposits">入款记录</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link" data-bs-toggle="tab" href="#withdrawals">下发记录</a>
+                    </li>
+                </ul>
+            </div>
+            <div class="card-body">
+                <div class="tab-content">
+                    <div class="tab-pane fade show active" id="deposits">
+                        <div class="table-responsive">
+                            <table class="table table-hover">
+                                <thead>
+                                    <tr>
+                                        <th>交易ID</th>
+                                        <th>时间</th>
+                                        <th>金额</th>
+                                        <th>操作人</th>
+                                        <th>回复人</th>
+                                    </tr>
+                                </thead>
+                                <tbody id="depositsList">
+                                </tbody>
+                            </table>
+                        </div>
+                        <nav>
+                            <ul class="pagination justify-content-center" id="depositsPagination">
+                            </ul>
+                        </nav>
+                    </div>
+                    <div class="tab-pane fade" id="withdrawals">
+                        <div class="table-responsive">
+                            <table class="table table-hover">
+                                <thead>
+                                    <tr>
+                                        <th>交易ID</th>
+                                        <th>时间</th>
+                                        <th>金额</th>
+                                        <th>操作人</th>
+                                        <th>回复人</th>
+                                    </tr>
+                                </thead>
+                                <tbody id="withdrawalsList">
+                                </tbody>
+                            </table>
+                        </div>
+                        <nav>
+                            <ul class="pagination justify-content-center" id="withdrawalsPagination">
+                            </ul>
+                        </nav>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="card">
+            <div class="card-header">
+                账单总结
+            </div>
+            <div class="card-body">
+                <table class="summary-table">
+                    <thead>
+                        <tr>
+                            <th>项目</th>
+                            <th>金额</th>
+                            <th>笔数</th>
+                        </tr>
+                    </thead>
+                    <tbody id="summaryTable">
+                        <tr>
+                            <td>总入款</td>
+                            <td id="totalDeposit">¥0.00</td>
+                            <td id="totalDepositCount">0</td>
+                        </tr>
+                        <tr>
+                            <td>入款费率</td>
+                            <td id="depositFeeRate">0%</td>
+                            <td>-</td>
+                        </tr>
+                        <tr>
+                            <td>应下发</td>
+                            <td id="shouldWithdraw">¥0.00</td>
+                            <td>-</td>
+                        </tr>
+                        <tr>
+                            <td>总下发</td>
+                            <td id="totalWithdraw">¥0.00</td>
+                            <td id="totalWithdrawCount">0</td>
+                        </tr>
+                        <tr>
+                            <td>下发费率</td>
+                            <td id="withdrawFeeRate">0%</td>
+                            <td>-</td>
+                        </tr>
+                        <tr>
+                            <td>下发单笔附加费</td>
+                            <td id="singleWithdrawFee">¥0.00</td>
+                            <td>-</td>
+                        </tr>
+                        <tr>
+                            <td>单笔附费加总计</td>
+                            <td id="totalWithdrawFee">¥0.00</td>
+                            <td>-</td>
+                        </tr>
+                        <tr>
+                            <td>余额</td>
+                            <td id="balance">¥0.00</td>
+                            <td>-</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
+    <script>
+        let currentPage = 1;
+        const pageSize = 10;
+
+        // 获取URL参数
+        function getUrlParams() {
+            const params = new URLSearchParams(window.location.search);
+            return {
+                groupId: params.get('groupId')
+            };
+        }
+
+        // 加载交易记录
+        async function loadTransactions(page = 1) {
+            try {
+                const { groupId } = getUrlParams();
+                if (!groupId) {
+                    alert('缺少群组ID参数');
+                    return;
+                }
+
+                console.log('正在加载数据,群组ID:', groupId);
+                const response = await fetch(`/api/public/transactions?page=${page}&limit=${pageSize}&groupId=${groupId}`);
+                console.log('API响应:', response);
+                
+                if (!response.ok) {
+                    const errorData = await response.json();
+                    throw new Error(errorData.message || '获取数据失败');
+                }
+
+                const data = await response.json();
+                console.log('获取到的数据:', data);
+                
+                updateTransactionsList(data.transactions);
+                updatePagination(data.total, page);
+                updateStatistics(data.statistics);
+                updateSummary(data.summary);
+            } catch (error) {
+                console.error('加载交易记录失败:', error);
+                alert(error.message || '加载数据失败,请稍后重试');
+            }
+        }
+
+        // 更新交易记录列表
+        function updateTransactionsList(transactions) {
+            const depositsList = document.getElementById('depositsList');
+            const withdrawalsList = document.getElementById('withdrawalsList');
+            
+            if (!transactions || transactions.length === 0) {
+                depositsList.innerHTML = '<tr><td colspan="5" class="text-center">暂无入款记录</td></tr>';
+                withdrawalsList.innerHTML = '<tr><td colspan="5" class="text-center">暂无下发记录</td></tr>';
+                return;
+            }
+            
+            const deposits = transactions.filter(t => t.type === 'deposit');
+            const withdrawals = transactions.filter(t => t.type === 'withdrawal');
+            
+            depositsList.innerHTML = deposits.length ? deposits.map(transaction => `
+                <tr>
+                    <td>${transaction.id}</td>
+                    <td>${new Date(transaction.time).toLocaleString('zh-CN')}</td>
+                    <td class="text-success">¥${parseFloat(transaction.amount).toFixed(2)}</td>
+                    <td>${transaction.operator_id || '-'}</td>
+                    <td>${transaction.replier_id || '-'}</td>
+                </tr>
+            `).join('') : '<tr><td colspan="5" class="text-center">暂无入款记录</td></tr>';
+            
+            withdrawalsList.innerHTML = withdrawals.length ? withdrawals.map(transaction => `
+                <tr>
+                    <td>${transaction.id}</td>
+                    <td>${new Date(transaction.time).toLocaleString('zh-CN')}</td>
+                    <td class="text-danger">¥${parseFloat(transaction.amount).toFixed(2)}</td>
+                    <td>${transaction.operator_id || '-'}</td>
+                    <td>${transaction.replier_id || '-'}</td>
+                </tr>
+            `).join('') : '<tr><td colspan="5" class="text-center">暂无下发记录</td></tr>';
+        }
+
+        // 更新分页
+        function updatePagination(total, currentPage) {
+            const totalPages = Math.ceil(total / pageSize);
+            const pagination = document.getElementById('depositsPagination');
+            const withdrawalsPagination = document.getElementById('withdrawalsPagination');
+            
+            let html = '';
+            
+            // 上一页
+            html += `
+                <li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
+                    <a class="page-link" href="#" onclick="loadTransactions(${currentPage - 1})">上一页</a>
+                </li>
+            `;
+            
+            // 页码
+            for (let i = 1; i <= totalPages; i++) {
+                html += `
+                    <li class="page-item ${i === currentPage ? 'active' : ''}">
+                        <a class="page-link" href="#" onclick="loadTransactions(${i})">${i}</a>
+                    </li>
+                `;
+            }
+            
+            // 下一页
+            html += `
+                <li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
+                    <a class="page-link" href="#" onclick="loadTransactions(${currentPage + 1})">下一页</a>
+                </li>
+            `;
+            
+            pagination.innerHTML = html;
+            withdrawalsPagination.innerHTML = html;
+        }
+
+        // 更新统计数据
+        function updateStatistics(statistics) {
+            if (!statistics) return;
+            
+            const formatAmount = (amount) => {
+                return amount ? `¥${parseFloat(amount).toFixed(2)}` : '¥0.00';
+            };
+            
+            document.getElementById('todayIncome').textContent = formatAmount(statistics.todayIncome);
+            document.getElementById('monthlyIncome').textContent = formatAmount(statistics.monthlyIncome);
+            document.getElementById('totalIncome').textContent = formatAmount(statistics.totalIncome);
+            document.getElementById('todayOrders').textContent = statistics.todayOrders || 0;
+        }
+
+        // 更新总结表格
+        function updateSummary(summary) {
+            if (!summary) return;
+            
+            const formatAmount = (amount) => {
+                return amount ? `¥${parseFloat(amount).toFixed(2)}` : '¥0.00';
+            };
+            
+            document.getElementById('totalDeposit').textContent = formatAmount(summary.totalDeposit);
+            document.getElementById('totalDepositCount').textContent = summary.depositCount || 0;
+            document.getElementById('depositFeeRate').textContent = `${summary.depositFeeRate || 0}%`;
+            document.getElementById('shouldWithdraw').textContent = formatAmount(summary.shouldWithdraw);
+            document.getElementById('totalWithdraw').textContent = formatAmount(summary.totalWithdraw);
+            document.getElementById('totalWithdrawCount').textContent = summary.withdrawCount || 0;
+            document.getElementById('withdrawFeeRate').textContent = `${summary.withdrawFeeRate || 0}%`;
+            document.getElementById('singleWithdrawFee').textContent = formatAmount(summary.singleWithdrawFee);
+            document.getElementById('totalWithdrawFee').textContent = formatAmount(summary.totalWithdrawFee);
+            document.getElementById('balance').textContent = formatAmount(summary.balance);
+        }
+
+        // 页面加载时加载数据
+        loadTransactions();
+    </script>
+</body>
+</html>