This commit is contained in:
2025-09-10 18:10:40 +08:00
parent 8530e97ab6
commit d50290e8fe
27 changed files with 2025 additions and 3913 deletions

View File

@@ -4,6 +4,8 @@ const { getDB } = require('../database');
const QRCode = require('qrcode');
const crypto = require('crypto');
const bcrypt = require('bcryptjs');
const { auth } = require('../middleware/auth');
const dayjs = require('dayjs');
// 获取浙江省所有区域列表
router.get('/regions', async (req, res) => {
@@ -22,7 +24,7 @@ router.get('/regions', async (req, res) => {
router.post('/apply', async (req, res) => {
try {
const { region_id, real_name, phone, id_card, contact_address } = req.body;
if (!region_id || !real_name || !phone || !id_card) {
return res.status(400).json({ success: false, message: '请填写完整信息' });
}
@@ -51,13 +53,13 @@ router.post('/apply', async (req, res) => {
let userId;
if (existingUser.length > 0) {
userId = existingUser[0].id;
// 检查该用户是否已申请过代理(包括所有状态)
const [existingUserAgent] = await getDB().execute(
'SELECT id, status, region_id FROM regional_agents WHERE user_id = ?',
[userId]
);
if (existingUserAgent.length > 0) {
const agentStatus = existingUserAgent[0].status;
if (agentStatus === 'active') {
@@ -73,7 +75,7 @@ router.post('/apply', async (req, res) => {
const bcrypt = require('bcryptjs');
const tempPassword = Math.random().toString(36).slice(-8); // 生成8位临时密码
const hashedPassword = await bcrypt.hash(tempPassword, 10);
const [userResult] = await getDB().execute(
'INSERT INTO users (username, password, phone, real_name, id_card, created_at) VALUES (?, ?, ?, ?, ?, NOW())',
[phone, hashedPassword, phone, real_name, id_card]
@@ -101,7 +103,7 @@ router.post('/apply', async (req, res) => {
router.post('/login', async (req, res) => {
try {
const { phone, password } = req.body;
if (!phone || !password) {
return res.status(400).json({ success: false, message: '请输入手机号和密码' });
}
@@ -121,7 +123,7 @@ router.post('/login', async (req, res) => {
}
const agent = agents[0];
// 验证密码
const isPasswordValid = await bcrypt.compare(password, agent.password);
if (!isPasswordValid) {
@@ -132,9 +134,9 @@ router.post('/login', async (req, res) => {
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const token = jwt.sign(
{
userId: agent.user_id,
username: agent.username || agent.phone,
{
userId: agent.user_id,
username: agent.username || agent.phone,
role: agent.role || 'agent',
agentId: agent.id
},
@@ -144,13 +146,13 @@ router.post('/login', async (req, res) => {
delete agent.password; // 不返回密码
res.json({
success: true,
res.json({
success: true,
data: {
...agent,
token
},
message: '登录成功'
},
message: '登录成功'
});
} catch (error) {
console.error('代理登录失败:', error);
@@ -158,56 +160,6 @@ router.post('/login', async (req, res) => {
}
});
// 生成注册二维码
router.post('/generate-invite-code', async (req, res) => {
try {
const { agent_id } = req.body;
if (!agent_id) {
return res.status(400).json({ success: false, message: '代理ID不能为空' });
}
// 验证代理是否存在且激活并获取对应的user_id
const [agents] = await getDB().execute(
'SELECT id, user_id FROM regional_agents WHERE id = ? AND status = "active"',
[parseInt(agent_id)]
);
if (agents.length === 0) {
return res.status(404).json({ success: false, message: '代理不存在或未激活' });
}
const userIdForAgent = agents[0].user_id;
// 生成唯一激活码
const code = crypto.randomBytes(8).toString('hex').toUpperCase();
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30天后过期
// 插入激活码记录created_by_admin_id设为3608agent_id使用user_id
await getDB().execute(
`INSERT INTO registration_codes (code, expires_at, created_by_admin_id, agent_id, is_used, created_at) VALUES ('${code}', '${expiresAt.toISOString().slice(0, 19).replace('T', ' ')}', 3608, ${userIdForAgent}, 0, NOW())`
);
// 生成二维码 - 使用注册页面URL不包含邀请码参数
const registerUrl = `${process.env.FRONTEND_URL || 'https://www.zrbjr.com/frontend'}/register`;
const qrCodeUrl = await QRCode.toDataURL(registerUrl);
res.json({
success: true,
data: {
code: code,
qr_code: qrCodeUrl,
expires_at: expiresAt
},
message: '二维码生成成功'
});
} catch (error) {
console.error('生成二维码失败:', error);
res.status(500).json({ success: false, message: '生成二维码失败' });
}
});
// 获取代理的商户列表(包含所有商户,标注早期商户状态)
router.get('/merchants/:agent_id', async (req, res) => {
try {
@@ -221,11 +173,11 @@ router.get('/merchants/:agent_id', async (req, res) => {
[parseInt(agent_id)]
);
const regionId = agentInfo[0].region_id;
if (!agentInfo || agentInfo.length === 0) {
return res.status(404).json({ success: false, message: '代理不存在' });
}
const agentCreatedAt = agentInfo[0].agent_created_at;
// 获取商户列表包含所有商户包括agent_merchants表中的和符合条件的早期商户
@@ -264,7 +216,7 @@ router.get('/merchants/:agent_id', async (req, res) => {
WHERE (am.agent_id = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user'))`,
[parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
);
// 获取早期商户统计从user表获取所有符合条件的早期商户
// 早期商户的判断条件1.早期商户注册时间比代理要早。2.代理商代理的区县与商户的区县一致
const [earlyMerchantStats] = await getDB().execute(
@@ -276,7 +228,7 @@ router.get('/merchants/:agent_id', async (req, res) => {
AND u.role = 'user'`,
[agentCreatedAt, parseInt(regionId)]
);
// 获取正常商户统计(包括代理关联的商户,排除符合条件的早期商户)
const [normalMerchantStats] = await getDB().execute(
`SELECT
@@ -287,8 +239,8 @@ router.get('/merchants/:agent_id', async (req, res) => {
[parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
);
res.json({
success: true,
res.json({
success: true,
data: {
merchants,
total: parseInt(countResult[0].total),
@@ -336,8 +288,8 @@ router.get('/commissions/:agent_id', async (req, res) => {
summary[0].paid_commission = summary[0].total_commission;
summary[0].pending_commission = 0;
res.json({
success: true,
res.json({
success: true,
data: {
commissions,
summary: summary[0],
@@ -379,20 +331,20 @@ router.get('/list', async (req, res) => {
try {
const { page = 1, limit = 10, status, region_id } = req.query;
const offset = (page - 1) * limit;
let whereClause = '1=1';
let params = [];
if (status) {
whereClause += ' AND ra.status = ?';
params.push(status);
}
if (region_id) {
whereClause += ' AND ra.region_id = ?';
params.push(region_id);
}
// 获取代理列表
const [agents] = await getDB().execute(
`SELECT ra.*, u.username, u.phone, u.real_name, u.created_at as user_created_at,
@@ -404,7 +356,7 @@ router.get('/list', async (req, res) => {
ORDER BY ra.created_at DESC
LIMIT ${limit} OFFSET ${offset}`
);
// 获取总数
const [countResult] = await getDB().execute(
`SELECT COUNT(*) as total
@@ -413,10 +365,10 @@ router.get('/list', async (req, res) => {
JOIN zhejiang_regions zr ON ra.region_id = zr.id
WHERE ${whereClause}`
);
const total = countResult[0].total;
const totalPages = Math.ceil(total / limit);
res.json({
success: true,
data: {
@@ -446,7 +398,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
const { period = '7d' } = req.query;
// 根据周期确定天数
let days;
switch (period) {
@@ -462,7 +414,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
default:
days = 7;
}
// 获取指定时间范围内的佣金趋势数据
const [trendData] = await getDB().execute(
`SELECT
@@ -475,33 +427,33 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
ORDER BY date ASC`,
[parseInt(agent_id), days]
);
// 填充缺失的日期(确保每天都有数据点)
const filledData = [];
const today = new Date();
for (let i = days - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
// 修复日期比较将数据库返回的Date对象转换为字符串进行比较
const existingData = trendData.find(item => {
const itemDateStr = item.date instanceof Date ?
item.date.toISOString().split('T')[0] :
const itemDateStr = item.date instanceof Date ?
item.date.toISOString().split('T')[0] :
item.date;
return itemDateStr === dateStr;
});
filledData.push({
date: dateStr,
amount: existingData ? parseFloat(existingData.amount) : 0
});
}
res.json({
success: true,
data: filledData
res.json({
success: true,
data: filledData
});
} catch (error) {
console.error('获取佣金趋势数据失败:', error);
@@ -518,7 +470,7 @@ router.get('/commission-trend/:agent_id', async (req, res) => {
router.get('/merchant-status/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
// 获取商户状态分布
const [statusData] = await getDB().execute(
`SELECT
@@ -536,10 +488,10 @@ router.get('/merchant-status/:agent_id', async (req, res) => {
ORDER BY count DESC`,
[parseInt(agent_id)]
);
res.json({
success: true,
data: statusData
res.json({
success: true,
data: statusData
});
} catch (error) {
console.error('获取商户状态分布数据失败:', error);
@@ -556,7 +508,7 @@ router.get('/merchant-status/:agent_id', async (req, res) => {
router.get('/detailed-stats/:agent_id', async (req, res) => {
try {
const { agent_id } = req.params;
// 获取基础统计数据
const [basicStats] = await getDB().execute(
`SELECT
@@ -568,7 +520,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
(SELECT COUNT(*) FROM agent_commission_records WHERE agent_id = ?) as total_commission_records`,
[parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id)]
);
// 获取本月佣金
const [monthlyStats] = await getDB().execute(
`SELECT
@@ -580,7 +532,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND MONTH(created_at) = MONTH(CURDATE())`,
[parseInt(agent_id)]
);
// 获取今日佣金
const [dailyStats] = await getDB().execute(
`SELECT
@@ -591,7 +543,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND DATE(created_at) = CURDATE()`,
[parseInt(agent_id)]
);
// 获取最近7天新增商户数
const [weeklyMerchants] = await getDB().execute(
`SELECT COUNT(*) as weekly_new_merchants
@@ -601,7 +553,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`,
[parseInt(agent_id)]
);
// 获取提现相关统计数据
const [withdrawalStats] = await getDB().execute(
`SELECT
@@ -619,7 +571,7 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
WHERE ra.id = ?`,
[parseInt(agent_id), parseInt(agent_id)]
);
// 合并所有统计数据
const stats = {
...basicStats[0],
@@ -632,10 +584,10 @@ router.get('/detailed-stats/:agent_id', async (req, res) => {
available_amount: 0
})
};
res.json({
success: true,
data: stats
res.json({
success: true,
data: stats
});
} catch (error) {
console.error('获取详细统计数据失败:', error);
@@ -658,17 +610,17 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const offset = (pageNum - 1) * limitNum;
// 检查代理是否存在
const [agentResult] = await getDB().execute(
'SELECT * FROM regional_agents WHERE id = ?',
[parseInt(agent_id)]
);
if (agentResult.length === 0) {
return res.status(404).json({ success: false, message: '代理不存在' });
}
// 查询商户转账记录
const transferQuery = `
SELECT
@@ -694,7 +646,7 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
LIMIT ${limitNum} OFFSET ${offset}
`;
const [transfers] = await getDB().execute(transferQuery, [parseInt(agent_id)]);
// 查询总数
const [totalResult] = await getDB().execute(
`SELECT COUNT(*) as total
@@ -703,9 +655,9 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
WHERE am.agent_id = ?`,
[parseInt(agent_id)]
);
const total = totalResult[0].total;
res.json({
success: true,
data: {
@@ -723,5 +675,45 @@ router.get('/merchants/:agent_id/transfers', async (req, res) => {
res.status(500).json({ success: false, message: '获取代理商户转账记录失败,请稍后再试' });
}
});
/**
* 获取分销列表
* @route GET /agents/distribution
* @returns {Object} 分销列表
*/
router.get('/distribution', auth, async (req, res) => {
try {
const { id } = req.user;
const { page = 1, size = 10 } = req.query;
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(size) || 10;
const offset = (page - 1) * size;
const [result] = await getDB().execute(
`SELECT real_name,phone,username,avatar,created_at FROM users WHERE inviter = ? ORDER BY created_at DESC
LIMIT ${size} OFFSET ${offset}`,
[parseInt(id)]
);
const [totalResult] = await getDB().execute(
`SELECT COUNT(*) as total FROM users WHERE inviter = ? `,
[parseInt(id)]
);
result.forEach(item => {
item.created_at = dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss');
})
const total = totalResult[0].total;
res.json({
success: true, data: result, pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
});
} catch (error) {
console.error('获取分销列表失败:', error);
res.status(500).json({ success: false, message: '获取分销列表失败' });
}
});
module.exports = router;