提交
This commit is contained in:
214
routes/agents.js
214
routes/agents.js
@@ -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设为3608,agent_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;
|
||||
Reference in New Issue
Block a user