Taio_O hai 1 mes
pai
achega
2a306904e5

+ 21 - 10
admin/config/initDb.js

@@ -1,5 +1,6 @@
 const { pool } = require('./database');
 const bcrypt = require('bcryptjs');
+const User = require('../models/User');
 
 const initDatabase = async () => {
     try {
@@ -97,17 +98,27 @@ const initDatabase = async () => {
         //     )
         // `);
 
-        // 创建默认管理员账户
-        const adminPassword = await bcrypt.hash('admin123', 10);
-        await pool.query(`
-            INSERT INTO users (username, password, role) 
-            VALUES ('admin', ?, 'admin')
-            ON DUPLICATE KEY UPDATE id=id
-        `, [adminPassword]);
-
-        console.log('数据库初始化成功');
+        // 检查是否存在管理员用户
+        const adminUser = await User.findByUsername('admin');
+        
+        if (!adminUser) {
+            console.log('创建默认管理员用户...');
+            // 创建默认管理员用户
+            const adminData = {
+                username: 'admin',
+                password: 'admin123', // 默认密码
+                role: 'admin'
+            };
+            
+            await User.create(adminData);
+            console.log('默认管理员用户创建成功');
+        } else {
+            console.log('管理员用户已存在');
+        }
+        
+        return true;
     } catch (error) {
-        console.error('数据库初始化失败:', error);
+        console.error('初始化数据库失败:', error);
         throw error;
     }
 };

+ 84 - 0
admin/controllers/settingsController.js

@@ -0,0 +1,84 @@
+const { pool } = require('../config/database');
+
+// @desc    获取系统设置
+// @route   GET /api/settings
+// @access  Private/Admin
+const getSettings = async (req, res) => {
+    try {
+        const [settings] = await pool.query('SELECT * FROM settings WHERE id = 1');
+        
+        if (settings.length === 0) {
+            // 如果没有设置记录,创建默认设置
+            await pool.query(`
+                INSERT INTO settings (id, site_name, admin_email, deposit_fee_rate, withdrawal_fee_rate)
+                VALUES (1, '后台管理系统', 'admin@example.com', 0.01, 0.01)
+            `);
+            
+            const [newSettings] = await pool.query('SELECT * FROM settings WHERE id = 1');
+            res.json(newSettings[0]);
+        } else {
+            res.json(settings[0]);
+        }
+    } catch (error) {
+        console.error('获取系统设置失败:', error);
+        res.status(500).json({ message: '服务器错误' });
+    }
+};
+
+// @desc    更新系统设置
+// @route   PUT /api/settings
+// @access  Private/Admin
+const updateSettings = async (req, res) => {
+    try {
+        const { siteName, adminEmail } = req.body;
+        
+        await pool.query(`
+            UPDATE settings 
+            SET site_name = ?, 
+                admin_email = ?
+            WHERE id = 1
+        `, [siteName, adminEmail]);
+        
+        const [settings] = await pool.query('SELECT * FROM settings WHERE id = 1');
+        res.json(settings[0]);
+    } catch (error) {
+        console.error('更新系统设置失败:', error);
+        res.status(500).json({ message: '服务器错误' });
+    }
+};
+
+// @desc    更新密码
+// @route   PUT /api/settings/password
+// @access  Private/Admin
+const updatePassword = async (req, res) => {
+    try {
+        const { currentPassword, newPassword } = req.body;
+        const userId = req.user.id;
+
+        // 验证当前密码
+        const [user] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);
+        if (!user.length) {
+            return res.status(404).json({ message: '用户不存在' });
+        }
+
+        const isMatch = await require('../models/User').comparePassword(currentPassword, user[0].password);
+        if (!isMatch) {
+            return res.status(401).json({ message: '当前密码错误' });
+        }
+
+        // 更新密码
+        const hashedPassword = await require('../models/User').hashPassword(newPassword);
+        await pool.query('UPDATE users SET password = ? WHERE id = ?', [hashedPassword, userId]);
+        
+        res.json({ message: '密码修改成功' });
+    } catch (error) {
+        console.error('更新密码失败:', error);
+        res.status(500).json({ message: '服务器错误' });
+    }
+};
+
+module.exports = {
+    getSettings,
+    updateSettings,
+    updatePassword
+}; 

+ 69 - 0
admin/controllers/statisticsController.js

@@ -0,0 +1,69 @@
+const Transaction = require('../models/Transaction');
+const Group = require('../models/Group');
+
+// @desc    获取统计数据
+// @route   GET /api/statistics
+// @access  Private/Admin
+const getStatistics = async (req, res) => {
+    try {
+        // 获取最近7天的交易数据
+        const endDate = new Date();
+        const startDate = new Date();
+        startDate.setDate(startDate.getDate() - 7);
+
+        const transactions = await Transaction.findAll(
+            { startDate, endDate },
+            1,
+            1000
+        );
+
+        // 处理交易数据,按日期分组
+        const transactionData = {};
+        const labels = [];
+        const data = [];
+
+        // 初始化最近7天的数据
+        for (let i = 6; i >= 0; i--) {
+            const date = new Date();
+            date.setDate(date.getDate() - i);
+            const dateStr = date.toISOString().split('T')[0];
+            transactionData[dateStr] = 0;
+            labels.push(dateStr);
+        }
+
+        // 统计每天的交易金额
+        transactions.transactions.forEach(transaction => {
+            const date = new Date(transaction.time).toISOString().split('T')[0];
+            if (transactionData[date] !== undefined) {
+                transactionData[date] += transaction.amount;
+            }
+        });
+
+        // 转换为数组格式
+        labels.forEach(label => {
+            data.push(transactionData[label]);
+        });
+
+        // 获取群组统计数据
+        const groups = await Group.findAll();
+        const groupData = {
+            labels: groups.map(group => group.groupName),
+            data: groups.map(group => group.isActive ? 1 : 0)
+        };
+
+        res.json({
+            transactions: {
+                labels,
+                data
+            },
+            groups: groupData
+        });
+    } catch (error) {
+        console.error('获取统计数据失败:', error);
+        res.status(500).json({ message: '服务器错误' });
+    }
+};
+
+module.exports = {
+    getStatistics
+}; 

+ 11 - 4
admin/controllers/userController.js

@@ -13,34 +13,41 @@ const generateToken = (id) => {
 // @access  Public
 const loginUser = async (req, res) => {
     try {
+        console.log('收到登录请求:', req.body);
         const { username, password } = req.body;
         
         if (!username || !password) {
+            console.log('缺少用户名或密码');
             return res.status(400).json({ message: '请提供用户名和密码' });
         }
 
         const user = await User.findByUsername(username);
-        console.log('查询到的用户:', user); // 调试日志
+        console.log('查询到的用户:', user ? '找到用户' : '未找到用户');
 
         if (!user) {
+            console.log('用户不存在');
             return res.status(401).json({ message: '用户名或密码错误' });
         }
 
         const isMatch = await User.comparePassword(password, user.password);
-        console.log('密码匹配结果:', isMatch); // 调试日志
+        console.log('密码匹配结果:', isMatch);
 
         if (isMatch) {
+            console.log('登录成功,生成token');
+            const token = generateToken(user.id);
+            console.log('生成的token:', token);
             res.json({
                 _id: user.id,
                 username: user.username,
                 role: user.role,
-                token: generateToken(user.id)
+                token: token
             });
         } else {
+            console.log('密码错误');
             res.status(401).json({ message: '用户名或密码错误' });
         }
     } catch (error) {
-        console.error('登录错误:', error); // 详细错误日志
+        console.error('登录错误:', error);
         res.status(500).json({ 
             message: '服务器错误',
             error: process.env.NODE_ENV === 'development' ? error.message : undefined

+ 16 - 14
admin/index.js

@@ -26,7 +26,7 @@ const bot = new TelegramBot(process.env.BOT_TOKEN, { polling: true });
 // 中间件
 app.use(cors());
 app.use(express.json());
-app.use(express.static('views'));
+app.use('/admin/views', express.static(path.join(__dirname, 'views')));
 
 // 路由
 app.get('/', (req, res) => {
@@ -36,6 +36,8 @@ app.get('/', (req, res) => {
 app.use('/api/users', require('./routes/userRoutes'));
 app.use('/api/groups', require('./routes/groupRoutes'));
 app.use('/api/transactions', require('./routes/transactionRoutes'));
+app.use('/api/statistics', require('./routes/statisticsRoutes'));
+app.use('/api/settings', require('./routes/settingsRoutes'));
 
 // 检查群组权限
 function isGroupAllowed(chatId) {
@@ -410,29 +412,29 @@ bot.onText(/\/bill/, async (msg) => {
 // 更新帮助命令
 bot.onText(/\/help/, (msg) => {
     const helpMessage = `
-🤖 *机器人使用指南*
+🤖 机器人使用指南
 
-📝 *基础命令*
-• /deposit <金额> - 记录入款
-• /withdraw <金额> - 记录下发
+📝 基础命令
+• /deposit 数字 - 记录入款
+• /withdraw 数字 - 记录下发
 • /bill - 查看当前账单
 • /help - 显示此帮助信息
 
-⚡️ *快捷命令*
-• +<金额> - 快速记录入款(例如:+2000)
-• -<金额> - 快速记录下发(例如:-2000)
+⚡️ 快捷命令
+• +数字 - 快速记录入款(例如:+2000)
+• -数字 - 快速记录下发(例如:-2000)
 
-👨‍💼 *管理员命令*
-• /addgroup <群组ID> - 添加允许的群组
-• /removegroup <群组ID> - 移除允许的群组
+👨‍💼 管理员命令
+• /addgroup 群组ID - 添加允许的群组
+• /removegroup 群组ID - 移除允许的群组
 • /listgroups - 列出所有允许的群组
 
-💡 *使用提示*
+💡 使用提示
 • 所有金额输入请使用数字
 • 账单信息实时更新
 • 如需帮助请联系管理员
     `;
-    sendMessage(msg.chat.id, helpMessage, { parse_mode: 'Markdown' });
+    sendMessage(msg.chat.id, helpMessage);
 });
 
 // 生成账单消息
@@ -540,7 +542,7 @@ bot.on('callback_query', async (callbackQuery) => {
         if (data.startsWith('bill_page_')) {
             const groupId = data.split('_')[2];
             await bot.answerCallbackQuery(callbackQuery.id, {
-                url: `${process.env.BILL_PAGE_BASE_URL}?groupId=${groupId}`
+                url: 'https://google.com'
             });
         } else if (data === 'business_contact') {
             await bot.answerCallbackQuery(callbackQuery.id, {

+ 4 - 3
admin/middleware/auth.js

@@ -4,17 +4,18 @@ const User = require('../models/User');
 const auth = async (req, res, next) => {
     try {
         const token = req.header('Authorization').replace('Bearer ', '');
-        const decoded = jwt.verify(token, process.env.JWT_SECRET);
-        const user = await User.findOne({ _id: decoded.id });
+        const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
+        const user = await User.findById(decoded.id);
 
         if (!user) {
-            throw new Error();
+            throw new Error('用户不存在');
         }
 
         req.token = token;
         req.user = user;
         next();
     } catch (error) {
+        console.error('认证失败:', error);
         res.status(401).json({ message: '请先登录' });
     }
 };

+ 158 - 59
admin/models/Transaction.js

@@ -3,75 +3,153 @@ const { pool } = require('../config/database');
 // 创建交易表
 const createTransactionTable = async () => {
     try {
-        await pool.query(`
-            CREATE TABLE IF NOT EXISTS transactions (
-                id INT AUTO_INCREMENT PRIMARY KEY,
-                group_id VARCHAR(50) NOT NULL,
-                group_name VARCHAR(100) NOT NULL,
-                type ENUM('deposit', 'withdrawal') NOT NULL,
-                amount DECIMAL(10,2) NOT NULL,
-                time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-                INDEX idx_group_time (group_id, time)
-            )
-        `);
+        // 先检查表是否存在
+        const [tables] = await pool.query('SHOW TABLES LIKE "transactions"');
+        if (tables.length === 0) {
+            // 如果表不存在,创建新表
+            await pool.query(`
+                CREATE TABLE transactions (
+                    id INT AUTO_INCREMENT PRIMARY KEY,
+                    group_id VARCHAR(50) NOT NULL,
+                    group_name VARCHAR(100) NOT NULL,
+                    type ENUM('deposit', 'withdrawal') NOT NULL COMMENT 'deposit:入款,withdrawal:出款',
+                    amount DECIMAL(10,2) NOT NULL,
+                    time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+                    remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
+                    operator_id INT NOT NULL COMMENT '操作人ID',
+                    INDEX idx_group_time (group_id, time),
+                    FOREIGN KEY (operator_id) REFERENCES users(id)
+                )
+            `);
+            console.log('交易表创建成功');
+        } else {
+            // 如果表存在,检查是否需要添加新字段
+            const [columns] = await pool.query('SHOW COLUMNS FROM transactions');
+            const columnNames = columns.map(col => col.Field);
+            
+            // 检查并添加缺失的字段
+            if (!columnNames.includes('operator_id')) {
+                try {
+                    // 先添加字段
+                    await pool.query(`
+                        ALTER TABLE transactions 
+                        ADD COLUMN operator_id INT NOT NULL DEFAULT 1 COMMENT '操作人ID' AFTER remark
+                    `);
+                    console.log('添加operator_id字段成功');
+
+                    // 然后添加外键约束
+                    await pool.query(`
+                        ALTER TABLE transactions 
+                        ADD CONSTRAINT fk_transaction_operator 
+                        FOREIGN KEY (operator_id) REFERENCES users(id)
+                    `);
+                    console.log('添加外键约束成功');
+                } catch (error) {
+                    console.error('添加operator_id字段或外键约束失败:', error);
+                    // 如果添加外键失败,至少保留字段
+                }
+            }
+            
+            if (!columnNames.includes('remark')) {
+                await pool.query(`
+                    ALTER TABLE transactions 
+                    ADD COLUMN remark VARCHAR(255) DEFAULT NULL COMMENT '备注' AFTER time
+                `);
+                console.log('添加remark字段成功');
+            }
+        }
     } catch (error) {
-        console.error('创建交易表失败:', error);
+        console.error('创建/更新交易表失败:', error);
+        throw error;
     }
 };
 
 // 初始化表
-createTransactionTable();
+createTransactionTable().catch(error => {
+    console.error('初始化交易表失败:', error);
+});
 
 // 交易相关方法
 const Transaction = {
     // 获取交易列表
     findAll: async (query = {}, page = 1, limit = 10) => {
-        let sql = 'SELECT * FROM transactions WHERE 1=1';
-        const params = [];
+        try {
+            console.log('查询参数:', query);
+            let sql = `
+                SELECT 
+                    t.*,
+                    u.username as operator_name
+                FROM transactions t
+                LEFT JOIN users u ON t.operator_id = u.id
+                WHERE 1=1
+            `;
+            const params = [];
 
-        if (query.startDate) {
-            sql += ' AND time >= ?';
-            params.push(query.startDate);
-        }
-        if (query.endDate) {
-            sql += ' AND time <= ?';
-            params.push(query.endDate);
-        }
-        if (query.type) {
-            sql += ' AND type = ?';
-            params.push(query.type);
-        }
-        if (query.groupId) {
-            sql += ' AND group_id = ?';
-            params.push(query.groupId);
-        }
+            if (query.startDate) {
+                sql += ' AND t.time >= ?';
+                params.push(query.startDate);
+            }
+            if (query.endDate) {
+                sql += ' AND t.time <= ?';
+                params.push(query.endDate);
+            }
+            if (query.type) {
+                sql += ' AND t.type = ?';
+                params.push(query.type);
+            }
+            if (query.groupId) {
+                sql += ' AND t.group_id = ?';
+                params.push(query.groupId);
+            }
 
-        // 获取总数
-        const [countResult] = await pool.query(
-            sql.replace('SELECT *', 'SELECT COUNT(*) as total'),
-            params
-        );
-        const total = countResult[0].total;
+            console.log('SQL查询:', sql);
+            console.log('参数:', params);
 
-        // 获取分页数据
-        sql += ' ORDER BY time DESC LIMIT ? OFFSET ?';
-        params.push(limit, (page - 1) * limit);
+            // 获取总数
+            const countSql = `
+                SELECT COUNT(*) as total 
+                FROM transactions t
+                WHERE 1=1
+                ${query.startDate ? 'AND t.time >= ?' : ''}
+                ${query.endDate ? 'AND t.time <= ?' : ''}
+                ${query.type ? 'AND t.type = ?' : ''}
+                ${query.groupId ? 'AND t.group_id = ?' : ''}
+            `;
+            
+            const [countResult] = await pool.query(countSql, params);
+            const total = countResult[0]?.total || 0;
 
-        const [rows] = await pool.query(sql, params);
+            // 获取分页数据
+            sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
+            params.push(limit, (page - 1) * limit);
 
-        return {
-            transactions: rows,
-            total,
-            page: parseInt(page),
-            pages: Math.ceil(total / limit)
-        };
+            const [rows] = await pool.query(sql, params);
+            console.log('查询结果数量:', rows.length);
+
+            return {
+                transactions: rows,
+                total,
+                page: parseInt(page),
+                pages: Math.ceil(total / limit)
+            };
+        } catch (error) {
+            console.error('查询交易列表失败:', error);
+            throw error;
+        }
     },
 
     // 创建交易
     create: async (transactionData) => {
         const [result] = await pool.query(
-            'INSERT INTO transactions (group_id, group_name, type, amount) VALUES (?, ?, ?, ?)',
-            [transactionData.groupId, transactionData.groupName, transactionData.type, transactionData.amount]
+            'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id) VALUES (?, ?, ?, ?, ?, ?)',
+            [
+                transactionData.groupId,
+                transactionData.groupName,
+                transactionData.type,
+                transactionData.amount,
+                transactionData.remark || null,
+                transactionData.operatorId
+            ]
         );
         return result.insertId;
     },
@@ -97,7 +175,15 @@ const Transaction = {
             pool.query('SELECT COUNT(*) as count FROM transactions').then(([rows]) => rows[0].count),
             pool.query('SELECT SUM(amount) as total FROM transactions').then(([rows]) => rows[0].total || 0),
             pool.query('SELECT COUNT(*) as count FROM transactions WHERE time >= ?', [today]).then(([rows]) => rows[0].count),
-            pool.query('SELECT * FROM transactions ORDER BY time DESC LIMIT 5').then(([rows]) => rows)
+            pool.query(`
+                SELECT 
+                    t.*,
+                    u.username as operator_name
+                FROM transactions t
+                LEFT JOIN users u ON t.operator_id = u.id
+                ORDER BY t.time DESC 
+                LIMIT 5
+            `).then(([rows]) => rows)
         ]);
 
         // 获取活跃群组
@@ -200,8 +286,10 @@ const Transaction = {
         let sql = `
             SELECT 
                 t.*,
+                u.username as operator_name,
                 DATE_FORMAT(t.time, '%Y-%m-%d %H:%i:%s') as formatted_time
-            FROM transactions t 
+            FROM transactions t
+            LEFT JOIN users u ON t.operator_id = u.id
             WHERE t.group_id = ?
         `;
         const params = [groupId];
@@ -220,10 +308,17 @@ const Transaction = {
         }
 
         // 获取总数
-        const [countResult] = await pool.query(
-            sql.replace('SELECT t.*, DATE_FORMAT(t.time, \'%Y-%m-%d %H:%i:%s\') as formatted_time', 'SELECT COUNT(*) as total'),
-            params
-        );
+        const countSql = `
+            SELECT COUNT(*) as total 
+            FROM transactions t
+            WHERE 1=1
+            ${startDate ? 'AND t.time >= ?' : ''}
+            ${endDate ? 'AND t.time <= ?' : ''}
+            ${type ? 'AND t.type = ?' : ''}
+            ${groupId ? 'AND t.group_id = ?' : ''}
+        `;
+        
+        const [countResult] = await pool.query(countSql, params);
         const total = countResult[0].total;
 
         // 获取分页数据
@@ -259,10 +354,14 @@ const Transaction = {
     // 根据ID查找交易记录
     findById: async (id) => {
         try {
-            const [rows] = await pool.query(
-                'SELECT * FROM transactions WHERE id = ?',
-                [id]
-            );
+            const [rows] = await pool.query(`
+                SELECT 
+                    t.*,
+                    u.username as operator_name
+                FROM transactions t
+                LEFT JOIN users u ON t.operator_id = u.id
+                WHERE t.id = ?
+            `, [id]);
             return rows[0] || null;
         } catch (error) {
             console.error('查询交易记录失败:', error);

+ 1 - 1
admin/routes/groupRoutes.js

@@ -12,7 +12,7 @@ const { auth, admin } = require('../middleware/auth');
 // 所有路由都需要认证和管理员权限
 router.use(auth, admin);
 
-router.route('/admin')
+router.route('/')
     .get(getGroups)
     .post(createGroup);
 

+ 15 - 0
admin/routes/settingsRoutes.js

@@ -0,0 +1,15 @@
+const express = require('express');
+const router = express.Router();
+const { getSettings, updateSettings, updatePassword } = require('../controllers/settingsController');
+const { auth, admin } = require('../middleware/auth');
+
+// 所有路由都需要认证和管理员权限
+router.use(auth, admin);
+
+router.route('/')
+    .get(getSettings)
+    .put(updateSettings);
+
+router.put('/password', updatePassword);
+
+module.exports = router; 

+ 11 - 0
admin/routes/statisticsRoutes.js

@@ -0,0 +1,11 @@
+const express = require('express');
+const router = express.Router();
+const { getStatistics } = require('../controllers/statisticsController');
+const { auth, admin } = require('../middleware/auth');
+
+// 所有路由都需要认证和管理员权限
+router.use(auth, admin);
+
+router.get('/', getStatistics);
+
+module.exports = router; 

+ 39 - 10
admin/views/dashboard.html

@@ -9,28 +9,30 @@
     <style>
         .sidebar {
             position: fixed;
-            top: 0;
+            top: 60px;
             bottom: 0;
             left: 0;
             z-index: 100;
-            padding: 48px 0 0;
+            padding: 0;
             box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
             background-color: #f8f9fa;
         }
         .sidebar-sticky {
             position: relative;
             top: 0;
-            height: calc(100vh - 48px);
+            height: calc(100vh - 60px);
             padding-top: .5rem;
             overflow-x: hidden;
             overflow-y: auto;
         }
         .navbar {
             box-shadow: 0 2px 4px rgba(0,0,0,.1);
+            height: 60px;
         }
         .main-content {
             margin-left: 240px;
             padding: 20px;
+            margin-top: 60px;
         }
         .nav-link {
             color: #333;
@@ -74,27 +76,27 @@
                 <div class="sidebar-sticky">
                     <ul class="nav flex-column">
                         <li class="nav-item">
-                            <a class="nav-link active" href="/dashboard.html" data-page="dashboard">
+                            <a class="nav-link active" href="/admin/views/dashboard.html" data-page="dashboard">
                                 <i class="bi bi-speedometer2"></i> 仪表板
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/groups.html" data-page="groups">
+                            <a class="nav-link" href="/admin/views/groups.html" data-page="groups">
                                 <i class="bi bi-people"></i> 群组管理
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/transactions.html" data-page="transactions">
+                            <a class="nav-link" href="/admin/views/transactions.html" data-page="transactions">
                                 <i class="bi bi-cash-stack"></i> 交易记录
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/statistics.html" data-page="statistics">
+                            <a class="nav-link" href="/admin/views/statistics.html" data-page="statistics">
                                 <i class="bi bi-graph-up"></i> 统计报表
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/settings.html" data-page="settings">
+                            <a class="nav-link" href="/admin/views/settings.html" data-page="settings">
                                 <i class="bi bi-gear"></i> 系统设置
                             </a>
                         </li>
@@ -192,16 +194,43 @@
         // 检查登录状态
         function checkAuth() {
             const token = localStorage.getItem('token');
+            console.log('检查登录状态,token:', token ? '存在' : '不存在');
+            
             if (!token) {
-                window.location.href = '/';
+                console.log('未找到token,跳转到登录页');
+                window.location.href = '/admin/views/login.html';
+                return;
             }
+
+            // 验证token是否有效
+            fetch('/api/users/profile', {
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('Token无效');
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('用户信息:', data);
+                // 可以在这里保存用户信息到全局变量
+                window.currentUser = data;
+            })
+            .catch(error => {
+                console.error('验证token时出错:', error);
+                localStorage.removeItem('token');
+                window.location.href = '/admin/views/login.html';
+            });
         }
 
         // 加载仪表板数据
         async function loadDashboardData() {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/transactions/dashboard', {
+                const response = await fetch('/api/transactions/dashboard', {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }

+ 58 - 25
admin/views/groups.html

@@ -9,28 +9,30 @@
     <style>
         .sidebar {
             position: fixed;
-            top: 0;
+            top: 60px;
             bottom: 0;
             left: 0;
             z-index: 100;
-            padding: 48px 0 0;
+            padding: 0;
             box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
             background-color: #f8f9fa;
         }
         .sidebar-sticky {
             position: relative;
             top: 0;
-            height: calc(100vh - 48px);
+            height: calc(100vh - 60px);
             padding-top: .5rem;
             overflow-x: hidden;
             overflow-y: auto;
         }
         .navbar {
             box-shadow: 0 2px 4px rgba(0,0,0,.1);
+            height: 60px;
         }
         .main-content {
             margin-left: 240px;
             padding: 20px;
+            margin-top: 60px;
         }
         .nav-link {
             color: #333;
@@ -68,27 +70,27 @@
                 <div class="sidebar-sticky">
                     <ul class="nav flex-column">
                         <li class="nav-item">
-                            <a class="nav-link" href="/dashboard.html" data-page="dashboard">
+                            <a class="nav-link" href="/admin/views/dashboard.html" data-page="dashboard">
                                 <i class="bi bi-speedometer2"></i> 仪表板
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link active" href="/groups.html" data-page="groups">
+                            <a class="nav-link active" href="/admin/views/groups.html" data-page="groups">
                                 <i class="bi bi-people"></i> 群组管理
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/transactions.html" data-page="transactions">
+                            <a class="nav-link" href="/admin/views/transactions.html" data-page="transactions">
                                 <i class="bi bi-cash-stack"></i> 交易记录
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/statistics.html" data-page="statistics">
+                            <a class="nav-link" href="/admin/views/statistics.html" data-page="statistics">
                                 <i class="bi bi-graph-up"></i> 统计报表
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/settings.html" data-page="settings">
+                            <a class="nav-link" href="/admin/views/settings.html" data-page="settings">
                                 <i class="bi bi-gear"></i> 系统设置
                             </a>
                         </li>
@@ -193,16 +195,43 @@
         // 检查登录状态
         function checkAuth() {
             const token = localStorage.getItem('token');
+            console.log('检查登录状态,token:', token ? '存在' : '不存在');
+            
             if (!token) {
-                window.location.href = '/';
+                console.log('未找到token,跳转到登录页');
+                window.location.href = '/admin/views/login.html';
+                return;
             }
+
+            // 验证token是否有效
+            fetch('/api/users/profile', {
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('Token无效');
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('用户信息:', data);
+                // 可以在这里保存用户信息到全局变量
+                window.currentUser = data;
+            })
+            .catch(error => {
+                console.error('验证token时出错:', error);
+                localStorage.removeItem('token');
+                window.location.href = '/admin/views/login.html';
+            });
         }
 
         // 加载群组列表
         async function loadGroups() {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/groups', {
+                const response = await fetch('/api/groups', {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }
@@ -226,19 +255,19 @@
             const groupsList = document.getElementById('groupsList');
             groupsList.innerHTML = groups.map(group => `
                 <tr>
-                    <td>${group.groupId}</td>
-                    <td>${group.groupName}</td>
+                    <td>${group.group_id}</td>
+                    <td>${group.group_name}</td>
                     <td>
-                        <span class="badge ${group.isActive ? 'bg-success' : 'bg-danger'}">
-                            ${group.isActive ? '启用' : '禁用'}
+                        <span class="badge ${group.is_active ? 'bg-success' : 'bg-danger'}">
+                            ${group.is_active ? '启用' : '禁用'}
                         </span>
                     </td>
-                    <td>${new Date(group.createdAt).toLocaleString()}</td>
+                    <td>${new Date(group.created_at).toLocaleString()}</td>
                     <td>
-                        <button class="btn btn-sm btn-primary btn-action" onclick="editGroup('${group._id}')">
+                        <button class="btn btn-sm btn-primary btn-action" onclick="editGroup('${group.id}')">
                             <i class="bi bi-pencil"></i>
                         </button>
-                        <button class="btn btn-sm btn-danger btn-action" onclick="deleteGroup('${group._id}')">
+                        <button class="btn btn-sm btn-danger btn-action" onclick="deleteGroup('${group.id}')">
                             <i class="bi bi-trash"></i>
                         </button>
                     </td>
@@ -253,13 +282,17 @@
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/groups', {
+                const response = await fetch('/api/groups', {
                     method: 'POST',
                     headers: {
                         'Authorization': `Bearer ${token}`,
                         'Content-Type': 'application/json'
                     },
-                    body: JSON.stringify({ groupId, groupName })
+                    body: JSON.stringify({ 
+                        groupId, 
+                        groupName,
+                        creatorId: window.currentUser._id
+                    })
                 });
 
                 if (response.ok) {
@@ -281,7 +314,7 @@
         async function editGroup(groupId) {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch(`/admin/api/groups/${groupId}`, {
+                const response = await fetch(`/api/groups/${groupId}`, {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }
@@ -289,9 +322,9 @@
 
                 if (response.ok) {
                     const group = await response.json();
-                    document.getElementById('editGroupId').value = group._id;
-                    document.getElementById('editGroupName').value = group.groupName;
-                    document.getElementById('editGroupStatus').checked = group.isActive;
+                    document.getElementById('editGroupId').value = group.id;
+                    document.getElementById('editGroupName').value = group.group_name;
+                    document.getElementById('editGroupStatus').checked = group.is_active;
 
                     const modal = new bootstrap.Modal(document.getElementById('editGroupModal'));
                     modal.show();
@@ -310,7 +343,7 @@
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch(`/admin/api/groups/${groupId}`, {
+                const response = await fetch(`/api/groups/${groupId}`, {
                     method: 'PUT',
                     headers: {
                         'Authorization': `Bearer ${token}`,
@@ -341,7 +374,7 @@
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch(`/admin/api/groups/${groupId}`, {
+                const response = await fetch(`/api/groups/${groupId}`, {
                     method: 'DELETE',
                     headers: {
                         'Authorization': `Bearer ${token}`

+ 6 - 1
admin/views/login.html

@@ -65,6 +65,7 @@
             const errorAlert = document.getElementById('errorAlert');
             
             try {
+                console.log('发送登录请求...');
                 const response = await fetch('/api/users/login', {
                     method: 'POST',
                     headers: {
@@ -74,13 +75,17 @@
                 });
 
                 const data = await response.json();
+                console.log('登录响应:', data);
 
                 if (response.ok) {
+                    console.log('登录成功,保存token');
                     // 保存 token 到 localStorage
                     localStorage.setItem('token', data.token);
+                    console.log('token已保存,准备跳转');
                     // 跳转到仪表板
-                    window.location.href = '/dashboard.html';
+                    window.location.href = '/admin/views/dashboard.html';
                 } else {
+                    console.log('登录失败:', data.message);
                     errorAlert.textContent = data.message || '登录失败';
                     errorAlert.style.display = 'block';
                 }

+ 41 - 12
admin/views/settings.html

@@ -9,28 +9,30 @@
     <style>
         .sidebar {
             position: fixed;
-            top: 0;
+            top: 60px;
             bottom: 0;
             left: 0;
             z-index: 100;
-            padding: 48px 0 0;
+            padding: 0;
             box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
             background-color: #f8f9fa;
         }
         .sidebar-sticky {
             position: relative;
             top: 0;
-            height: calc(100vh - 48px);
+            height: calc(100vh - 60px);
             padding-top: .5rem;
             overflow-x: hidden;
             overflow-y: auto;
         }
         .navbar {
             box-shadow: 0 2px 4px rgba(0,0,0,.1);
+            height: 60px;
         }
         .main-content {
             margin-left: 240px;
             padding: 20px;
+            margin-top: 60px;
         }
         .nav-link {
             color: #333;
@@ -65,27 +67,27 @@
                 <div class="sidebar-sticky">
                     <ul class="nav flex-column">
                         <li class="nav-item">
-                            <a class="nav-link" href="/dashboard.html" data-page="dashboard">
+                            <a class="nav-link" href="/admin/views/dashboard.html" data-page="dashboard">
                                 <i class="bi bi-speedometer2"></i> 仪表板
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/groups.html" data-page="groups">
+                            <a class="nav-link" href="/admin/views/groups.html" data-page="groups">
                                 <i class="bi bi-people"></i> 群组管理
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/transactions.html" data-page="transactions">
+                            <a class="nav-link" href="/admin/views/transactions.html" data-page="transactions">
                                 <i class="bi bi-cash-stack"></i> 交易记录
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/statistics.html" data-page="statistics">
+                            <a class="nav-link" href="/admin/views/statistics.html" data-page="statistics">
                                 <i class="bi bi-graph-up"></i> 统计报表
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link active" href="/settings.html" data-page="settings">
+                            <a class="nav-link active" href="/admin/views/settings.html" data-page="settings">
                                 <i class="bi bi-gear"></i> 系统设置
                             </a>
                         </li>
@@ -149,16 +151,43 @@
         // 检查登录状态
         function checkAuth() {
             const token = localStorage.getItem('token');
+            console.log('检查登录状态,token:', token ? '存在' : '不存在');
+            
             if (!token) {
-                window.location.href = '/';
+                console.log('未找到token,跳转到登录页');
+                window.location.href = '/admin/views/login.html';
+                return;
             }
+
+            // 验证token是否有效
+            fetch('/api/users/profile', {
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('Token无效');
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('用户信息:', data);
+                // 可以在这里保存用户信息到全局变量
+                window.currentUser = data;
+            })
+            .catch(error => {
+                console.error('验证token时出错:', error);
+                localStorage.removeItem('token');
+                window.location.href = '/admin/views/login.html';
+            });
         }
 
         // 加载设置
         async function loadSettings() {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/settings', {
+                const response = await fetch('/api/settings', {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }
@@ -181,7 +210,7 @@
             e.preventDefault();
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/settings', {
+                const response = await fetch('/api/settings', {
                     method: 'PUT',
                     headers: {
                         'Authorization': `Bearer ${token}`,
@@ -218,7 +247,7 @@
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/settings/password', {
+                const response = await fetch('/api/settings/password', {
                     method: 'PUT',
                     headers: {
                         'Authorization': `Bearer ${token}`,

+ 39 - 10
admin/views/statistics.html

@@ -9,28 +9,30 @@
     <style>
         .sidebar {
             position: fixed;
-            top: 0;
+            top: 60px;
             bottom: 0;
             left: 0;
             z-index: 100;
-            padding: 48px 0 0;
+            padding: 0;
             box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
             background-color: #f8f9fa;
         }
         .sidebar-sticky {
             position: relative;
             top: 0;
-            height: calc(100vh - 48px);
+            height: calc(100vh - 60px);
             padding-top: .5rem;
             overflow-x: hidden;
             overflow-y: auto;
         }
         .navbar {
             box-shadow: 0 2px 4px rgba(0,0,0,.1);
+            height: 60px;
         }
         .main-content {
             margin-left: 240px;
             padding: 20px;
+            margin-top: 60px;
         }
         .nav-link {
             color: #333;
@@ -65,27 +67,27 @@
                 <div class="sidebar-sticky">
                     <ul class="nav flex-column">
                         <li class="nav-item">
-                            <a class="nav-link" href="/dashboard.html" data-page="dashboard">
+                            <a class="nav-link" href="/admin/views/dashboard.html" data-page="dashboard">
                                 <i class="bi bi-speedometer2"></i> 仪表板
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/groups.html" data-page="groups">
+                            <a class="nav-link" href="/admin/views/groups.html" data-page="groups">
                                 <i class="bi bi-people"></i> 群组管理
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/transactions.html" data-page="transactions">
+                            <a class="nav-link" href="/admin/views/transactions.html" data-page="transactions">
                                 <i class="bi bi-cash-stack"></i> 交易记录
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link active" href="/statistics.html" data-page="statistics">
+                            <a class="nav-link active" href="/admin/views/statistics.html" data-page="statistics">
                                 <i class="bi bi-graph-up"></i> 统计报表
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/settings.html" data-page="settings">
+                            <a class="nav-link" href="/admin/views/settings.html" data-page="settings">
                                 <i class="bi bi-gear"></i> 系统设置
                             </a>
                         </li>
@@ -126,16 +128,43 @@
         // 检查登录状态
         function checkAuth() {
             const token = localStorage.getItem('token');
+            console.log('检查登录状态,token:', token ? '存在' : '不存在');
+            
             if (!token) {
-                window.location.href = '/';
+                console.log('未找到token,跳转到登录页');
+                window.location.href = '/admin/views/login.html';
+                return;
             }
+
+            // 验证token是否有效
+            fetch('/api/users/profile', {
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('Token无效');
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('用户信息:', data);
+                // 可以在这里保存用户信息到全局变量
+                window.currentUser = data;
+            })
+            .catch(error => {
+                console.error('验证token时出错:', error);
+                localStorage.removeItem('token');
+                window.location.href = '/admin/views/login.html';
+            });
         }
 
         // 加载统计数据
         async function loadStatistics() {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/statistics', {
+                const response = await fetch('/api/statistics', {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }

+ 63 - 19
admin/views/transactions.html

@@ -9,28 +9,30 @@
     <style>
         .sidebar {
             position: fixed;
-            top: 0;
+            top: 60px;
             bottom: 0;
             left: 0;
             z-index: 100;
-            padding: 48px 0 0;
+            padding: 0;
             box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
             background-color: #f8f9fa;
         }
         .sidebar-sticky {
             position: relative;
             top: 0;
-            height: calc(100vh - 48px);
+            height: calc(100vh - 60px);
             padding-top: .5rem;
             overflow-x: hidden;
             overflow-y: auto;
         }
         .navbar {
             box-shadow: 0 2px 4px rgba(0,0,0,.1);
+            height: 60px;
         }
         .main-content {
             margin-left: 240px;
             padding: 20px;
+            margin-top: 60px;
         }
         .nav-link {
             color: #333;
@@ -74,27 +76,27 @@
                 <div class="sidebar-sticky">
                     <ul class="nav flex-column">
                         <li class="nav-item">
-                            <a class="nav-link" href="/dashboard.html" data-page="dashboard">
+                            <a class="nav-link" href="/admin/views/dashboard.html" data-page="dashboard">
                                 <i class="bi bi-speedometer2"></i> 仪表板
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/groups.html" data-page="groups">
+                            <a class="nav-link" href="/admin/views/groups.html" data-page="groups">
                                 <i class="bi bi-people"></i> 群组管理
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link active" href="/transactions.html" data-page="transactions">
+                            <a class="nav-link active" href="/admin/views/transactions.html" data-page="transactions">
                                 <i class="bi bi-cash-stack"></i> 交易记录
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/statistics.html" data-page="statistics">
+                            <a class="nav-link" href="/admin/views/statistics.html" data-page="statistics">
                                 <i class="bi bi-graph-up"></i> 统计报表
                             </a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="/settings.html" data-page="settings">
+                            <a class="nav-link" href="/admin/views/settings.html" data-page="settings">
                                 <i class="bi bi-gear"></i> 系统设置
                             </a>
                         </li>
@@ -156,6 +158,8 @@
                                         <th>群组</th>
                                         <th>类型</th>
                                         <th>金额</th>
+                                        <th>操作人</th>
+                                        <th>备注</th>
                                         <th>操作</th>
                                     </tr>
                                 </thead>
@@ -199,6 +203,10 @@
                             <label for="amount" class="form-label">金额</label>
                             <input type="number" class="form-control" id="amount" step="0.01" required>
                         </div>
+                        <div class="mb-3">
+                            <label for="remark" class="form-label">备注</label>
+                            <input type="text" class="form-control" id="remark">
+                        </div>
                     </form>
                 </div>
                 <div class="modal-footer">
@@ -217,16 +225,43 @@
         // 检查登录状态
         function checkAuth() {
             const token = localStorage.getItem('token');
+            console.log('检查登录状态,token:', token ? '存在' : '不存在');
+            
             if (!token) {
-                window.location.href = '/';
+                console.log('未找到token,跳转到登录页');
+                window.location.href = '/admin/views/login.html';
+                return;
             }
+
+            // 验证token是否有效
+            fetch('/api/users/profile', {
+                headers: {
+                    'Authorization': `Bearer ${token}`
+                }
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('Token无效');
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('用户信息:', data);
+                // 可以在这里保存用户信息到全局变量
+                window.currentUser = data;
+            })
+            .catch(error => {
+                console.error('验证token时出错:', error);
+                localStorage.removeItem('token');
+                window.location.href = '/admin/views/login.html';
+            });
         }
 
         // 加载群组列表
         async function loadGroups() {
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/groups', {
+                const response = await fetch('/api/groups', {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }
@@ -238,7 +273,7 @@
                     const transactionGroupSelect = document.getElementById('transactionGroupId');
                     
                     const options = groups.map(group => 
-                        `<option value="${group.groupId}">${group.groupName}</option>`
+                        `<option value="${group.group_id}">${group.group_name}</option>`
                     ).join('');
                     
                     groupSelect.innerHTML = '<option value="">全部</option>' + options;
@@ -267,7 +302,7 @@
                     ...(groupId && { groupId })
                 });
 
-                const response = await fetch(`/admin/api/transactions?${queryParams}`, {
+                const response = await fetch(`/api/transactions?${queryParams}`, {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }
@@ -293,15 +328,17 @@
             transactionsList.innerHTML = transactions.map(transaction => `
                 <tr>
                     <td>${new Date(transaction.time).toLocaleString()}</td>
-                    <td>${transaction.groupName}</td>
+                    <td>${transaction.group_name}</td>
                     <td>
                         <span class="badge ${transaction.type === 'deposit' ? 'bg-success' : 'bg-danger'}">
-                            ${transaction.type === 'deposit' ? '入款' : '下发'}
+                            ${transaction.type === 'deposit' ? '入款' : '出款'}
                         </span>
                     </td>
                     <td>¥${transaction.amount.toFixed(2)}</td>
+                    <td>${transaction.operator_name || '-'}</td>
+                    <td>${transaction.remark || '-'}</td>
                     <td>
-                        <button class="btn btn-sm btn-danger" onclick="deleteTransaction('${transaction._id}')">
+                        <button class="btn btn-sm btn-danger" onclick="deleteTransaction('${transaction.id}')">
                             <i class="bi bi-trash"></i>
                         </button>
                     </td>
@@ -347,16 +384,23 @@
             const groupId = document.getElementById('transactionGroupId').value;
             const type = document.getElementById('transactionType').value;
             const amount = parseFloat(document.getElementById('amount').value);
+            const remark = document.getElementById('remark').value;
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch('/admin/api/transactions', {
+                const response = await fetch('/api/transactions', {
                     method: 'POST',
                     headers: {
                         'Authorization': `Bearer ${token}`,
                         'Content-Type': 'application/json'
                     },
-                    body: JSON.stringify({ groupId, type, amount })
+                    body: JSON.stringify({ 
+                        groupId: groupId,
+                        type: type,
+                        amount: amount,
+                        remark: remark,
+                        operatorId: window.currentUser._id
+                    })
                 });
 
                 if (response.ok) {
@@ -382,7 +426,7 @@
 
             try {
                 const token = localStorage.getItem('token');
-                const response = await fetch(`/admin/api/transactions/${transactionId}`, {
+                const response = await fetch(`/api/transactions/${transactionId}`, {
                     method: 'DELETE',
                     headers: {
                         'Authorization': `Bearer ${token}`
@@ -417,7 +461,7 @@
                     ...(groupId && { groupId })
                 });
 
-                const response = await fetch(`/admin/api/transactions/export?${queryParams}`, {
+                const response = await fetch(`/api/transactions/export?${queryParams}`, {
                     headers: {
                         'Authorization': `Bearer ${token}`
                     }