727 lines
25 KiB
JavaScript
727 lines
25 KiB
JavaScript
|
|
const express = require('express');
|
|||
|
|
const router = express.Router();
|
|||
|
|
const { getDB } = require('../database');
|
|||
|
|
const QRCode = require('qrcode');
|
|||
|
|
const crypto = require('crypto');
|
|||
|
|
const bcrypt = require('bcryptjs');
|
|||
|
|
|
|||
|
|
// 获取浙江省所有区域列表
|
|||
|
|
router.get('/regions', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const [regions] = await getDB().execute(
|
|||
|
|
'SELECT * FROM zhejiang_regions ORDER BY city_name, district_name'
|
|||
|
|
);
|
|||
|
|
res.json({ success: true, data: regions });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取区域列表失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取区域列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 申请成为区域代理
|
|||
|
|
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: '请填写完整信息' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查该区域是否已有代理(包括所有状态,不仅仅是active)
|
|||
|
|
const [existingRegionAgent] = await getDB().execute(
|
|||
|
|
'SELECT id, status FROM regional_agents WHERE region_id = ? AND status IN ("pending", "active")',
|
|||
|
|
[region_id]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (existingRegionAgent.length > 0) {
|
|||
|
|
const status = existingRegionAgent[0].status;
|
|||
|
|
if (status === 'active') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该区域已有激活的代理,每个区域只能有一个代理账号' });
|
|||
|
|
} else if (status === 'pending') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该区域已有待审核的代理申请,每个区域只能有一个代理账号' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查手机号是否已存在用户
|
|||
|
|
const [existingUser] = await getDB().execute(
|
|||
|
|
'SELECT id FROM users WHERE phone = ?',
|
|||
|
|
[phone]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
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') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该用户已是其他区域的激活代理,一个用户只能申请一个区域的代理' });
|
|||
|
|
} else if (agentStatus === 'pending') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该用户已有待审核的代理申请,一个用户只能申请一个区域的代理' });
|
|||
|
|
} else if (agentStatus === 'suspended' || agentStatus === 'terminated') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该用户的代理资格已被暂停或终止,无法重新申请' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 创建新用户(为代理申请用户生成临时密码)
|
|||
|
|
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]
|
|||
|
|
);
|
|||
|
|
userId = userResult.insertId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成代理编码
|
|||
|
|
const agentCode = 'AG' + Date.now().toString().slice(-8);
|
|||
|
|
|
|||
|
|
// 创建代理申请
|
|||
|
|
await getDB().execute(
|
|||
|
|
'INSERT INTO regional_agents (user_id, region_id, agent_code, status, created_at) VALUES (?, ?, ?, "pending", NOW())',
|
|||
|
|
[userId, region_id, agentCode]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({ success: true, message: '申请提交成功,请等待审核' });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('申请代理失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '申请失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 代理登录
|
|||
|
|
router.post('/login', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { phone, password } = req.body;
|
|||
|
|
|
|||
|
|
if (!phone || !password) {
|
|||
|
|
return res.status(400).json({ success: false, message: '请输入手机号和密码' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先查询用户和代理信息(包含密码用于验证)
|
|||
|
|
const [agents] = await getDB().execute(
|
|||
|
|
`SELECT ra.*, u.id as user_id, u.username, u.phone, u.real_name, u.password, u.role, zr.city_name, zr.district_name
|
|||
|
|
FROM regional_agents ra
|
|||
|
|
JOIN users u ON ra.user_id = u.id
|
|||
|
|
JOIN zhejiang_regions zr ON ra.region_id = zr.id
|
|||
|
|
WHERE u.phone = ? AND ra.status = "active"`,
|
|||
|
|
[phone]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (agents.length === 0) {
|
|||
|
|
return res.status(401).json({ success: false, message: '手机号或密码错误,或账户未激活' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const agent = agents[0];
|
|||
|
|
|
|||
|
|
// 验证密码
|
|||
|
|
const isPasswordValid = await bcrypt.compare(password, agent.password);
|
|||
|
|
if (!isPasswordValid) {
|
|||
|
|
return res.status(401).json({ success: false, message: '手机号或密码错误,或账户未激活' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成JWT token
|
|||
|
|
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,
|
|||
|
|
role: agent.role || 'agent',
|
|||
|
|
agentId: agent.id
|
|||
|
|
},
|
|||
|
|
JWT_SECRET,
|
|||
|
|
{ expiresIn: '24h' }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
delete agent.password; // 不返回密码
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
...agent,
|
|||
|
|
token
|
|||
|
|
},
|
|||
|
|
message: '登录成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('代理登录失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '登录失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 生成注册二维码
|
|||
|
|
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 {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
const { page = 1, limit = 10 } = req.query;
|
|||
|
|
const offset = (page - 1) * limit;
|
|||
|
|
|
|||
|
|
// 首先获取代理的注册时间
|
|||
|
|
const [agentInfo] = await getDB().execute(
|
|||
|
|
`SELECT ra.created_at as agent_created_at, ra.region_id FROM regional_agents ra WHERE ra.id = ?`,
|
|||
|
|
[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表中的和符合条件的早期商户)
|
|||
|
|
const [merchants] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
u.id,
|
|||
|
|
u.username,
|
|||
|
|
u.phone,
|
|||
|
|
u.real_name,
|
|||
|
|
u.created_at,
|
|||
|
|
u.audit_status,
|
|||
|
|
IFNULL(am.created_at, '未关联') as joined_at,
|
|||
|
|
CASE
|
|||
|
|
WHEN u.created_at < ? AND u.district_id = ? THEN 1
|
|||
|
|
ELSE 0
|
|||
|
|
END as is_early_merchant,
|
|||
|
|
CASE
|
|||
|
|
WHEN u.created_at < ? AND u.district_id = ? THEN '早期商户(不记录佣金)'
|
|||
|
|
ELSE '正常商户'
|
|||
|
|
END as merchant_status,
|
|||
|
|
(SELECT COUNT(*) FROM matching_orders WHERE initiator_id = u.id AND status = 'completed') as completed_matches,
|
|||
|
|
(SELECT COUNT(*) FROM agent_commission_records WHERE merchant_id = u.id AND agent_id = ?) as commission_count
|
|||
|
|
FROM users u
|
|||
|
|
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
|
|||
|
|
WHERE (am.agent_id = ? OR (u.created_at < ? AND u.district_id = ? AND u.role = 'user'))
|
|||
|
|
ORDER BY u.created_at DESC
|
|||
|
|
LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`,
|
|||
|
|
[agentCreatedAt, parseInt(regionId), agentCreatedAt, parseInt(regionId), parseInt(agent_id), parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取总数(包括代理关联的商户和符合条件的早期商户)
|
|||
|
|
const [countResult] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as total
|
|||
|
|
FROM users u
|
|||
|
|
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
|
|||
|
|
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(
|
|||
|
|
`SELECT
|
|||
|
|
COUNT(*) as early_merchant_count
|
|||
|
|
FROM users u
|
|||
|
|
WHERE u.created_at < ?
|
|||
|
|
AND u.district_id = ?
|
|||
|
|
AND u.role = 'user'`,
|
|||
|
|
[agentCreatedAt, parseInt(regionId)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取正常商户统计(包括代理关联的商户,排除符合条件的早期商户)
|
|||
|
|
const [normalMerchantStats] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
COUNT(*) as normal_merchant_count
|
|||
|
|
FROM users u
|
|||
|
|
LEFT JOIN agent_merchants am ON u.id = am.merchant_id AND am.agent_id = ?
|
|||
|
|
WHERE (am.agent_id = ? AND (u.created_at >= ? OR u.district_id != ?))`,
|
|||
|
|
[parseInt(agent_id), parseInt(agent_id), agentCreatedAt, parseInt(regionId)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
merchants,
|
|||
|
|
total: parseInt(countResult[0].total),
|
|||
|
|
page: parseInt(page),
|
|||
|
|
limit: parseInt(limit),
|
|||
|
|
stats: {
|
|||
|
|
total_merchants: parseInt(countResult[0].total),
|
|||
|
|
early_merchants: parseInt(earlyMerchantStats[0].early_merchant_count),
|
|||
|
|
normal_merchants: parseInt(normalMerchantStats[0].normal_merchant_count)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取商户列表失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取商户列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取代理的佣金记录
|
|||
|
|
router.get('/commissions/:agent_id', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
const { page = 1, limit = 10 } = req.query;
|
|||
|
|
const offset = (page - 1) * limit;
|
|||
|
|
|
|||
|
|
// 获取佣金记录
|
|||
|
|
const [commissions] = await getDB().execute(
|
|||
|
|
`SELECT acr.*, u.username, u.real_name
|
|||
|
|
FROM agent_commission_records acr
|
|||
|
|
JOIN users u ON acr.merchant_id = u.id
|
|||
|
|
WHERE acr.agent_id = ${parseInt(agent_id)}
|
|||
|
|
ORDER BY acr.created_at DESC
|
|||
|
|
LIMIT ${parseInt(limit)} OFFSET ${parseInt(offset)}`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取总数和总佣金
|
|||
|
|
const [summary] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as total_records,
|
|||
|
|
COALESCE(SUM(commission_amount), 0) as total_commission
|
|||
|
|
FROM agent_commission_records
|
|||
|
|
WHERE agent_id = ${parseInt(agent_id)}`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 由于agent_commission_records表没有status字段,设置默认值
|
|||
|
|
summary[0].paid_commission = summary[0].total_commission;
|
|||
|
|
summary[0].pending_commission = 0;
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
commissions,
|
|||
|
|
summary: summary[0],
|
|||
|
|
page: parseInt(page),
|
|||
|
|
limit: parseInt(limit)
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取佣金记录失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取佣金记录失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取代理统计信息
|
|||
|
|
router.get('/stats/:agent_id', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
|
|||
|
|
// 获取统计数据
|
|||
|
|
const [stats] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants WHERE agent_id = ${parseInt(agent_id)}) as total_merchants,
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ${parseInt(agent_id)} AND u.audit_status = 'approved') as approved_merchants,
|
|||
|
|
(SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ${parseInt(agent_id)}) as total_commission,
|
|||
|
|
(SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ${parseInt(agent_id)}) as paid_commission,
|
|||
|
|
(SELECT COUNT(*) FROM registration_codes rc JOIN regional_agents ra ON rc.agent_id = ra.user_id WHERE ra.id = ${parseInt(agent_id)} AND rc.is_used = 1) as used_codes,
|
|||
|
|
(SELECT COUNT(*) FROM registration_codes rc JOIN regional_agents ra ON rc.agent_id = ra.user_id WHERE ra.id = ${parseInt(agent_id)} AND rc.is_used = 0 AND rc.expires_at > NOW()) as active_codes`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({ success: true, data: stats[0] });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取统计信息失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取统计信息失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取代理列表
|
|||
|
|
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,
|
|||
|
|
zr.city_name, zr.district_name, zr.region_code
|
|||
|
|
FROM regional_agents ra
|
|||
|
|
JOIN users u ON ra.user_id = u.id
|
|||
|
|
JOIN zhejiang_regions zr ON ra.region_id = zr.id
|
|||
|
|
WHERE ${whereClause}
|
|||
|
|
ORDER BY ra.created_at DESC
|
|||
|
|
LIMIT ${limit} OFFSET ${offset}`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取总数
|
|||
|
|
const [countResult] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as total
|
|||
|
|
FROM regional_agents ra
|
|||
|
|
JOIN users u ON ra.user_id = u.id
|
|||
|
|
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: {
|
|||
|
|
agents,
|
|||
|
|
pagination: {
|
|||
|
|
page: parseInt(page),
|
|||
|
|
limit: parseInt(limit),
|
|||
|
|
total,
|
|||
|
|
totalPages
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取代理列表失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取代理列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取代理佣金趋势数据
|
|||
|
|
* @route GET /agents/commission-trend/:agent_id
|
|||
|
|
* @param {string} agent_id - 代理ID
|
|||
|
|
* @param {string} period - 时间周期 (7d, 30d, 3m)
|
|||
|
|
* @returns {Object} 佣金趋势数据
|
|||
|
|
*/
|
|||
|
|
router.get('/commission-trend/:agent_id', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
const { period = '7d' } = req.query;
|
|||
|
|
|
|||
|
|
// 根据周期确定天数
|
|||
|
|
let days;
|
|||
|
|
switch (period) {
|
|||
|
|
case '7d':
|
|||
|
|
days = 7;
|
|||
|
|
break;
|
|||
|
|
case '30d':
|
|||
|
|
days = 30;
|
|||
|
|
break;
|
|||
|
|
case '3m':
|
|||
|
|
days = 90;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
days = 7;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取指定时间范围内的佣金趋势数据
|
|||
|
|
const [trendData] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
DATE(created_at) as date,
|
|||
|
|
COALESCE(SUM(commission_amount), 0) as amount
|
|||
|
|
FROM agent_commission_records
|
|||
|
|
WHERE agent_id = ?
|
|||
|
|
AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
|||
|
|
GROUP BY DATE(created_at)
|
|||
|
|
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] :
|
|||
|
|
item.date;
|
|||
|
|
return itemDateStr === dateStr;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
filledData.push({
|
|||
|
|
date: dateStr,
|
|||
|
|
amount: existingData ? parseFloat(existingData.amount) : 0
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: filledData
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取佣金趋势数据失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取佣金趋势数据失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取代理商户状态分布数据
|
|||
|
|
* @route GET /agents/merchant-status/:agent_id
|
|||
|
|
* @param {string} agent_id - 代理ID
|
|||
|
|
* @returns {Object} 商户状态分布数据
|
|||
|
|
*/
|
|||
|
|
router.get('/merchant-status/:agent_id', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
|
|||
|
|
// 获取商户状态分布
|
|||
|
|
const [statusData] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
CASE
|
|||
|
|
WHEN u.audit_status = 'approved' THEN '已审核'
|
|||
|
|
WHEN u.audit_status = 'pending' THEN '待审核'
|
|||
|
|
WHEN u.audit_status = 'rejected' THEN '已拒绝'
|
|||
|
|
ELSE '未知状态'
|
|||
|
|
END as status,
|
|||
|
|
COUNT(*) as count
|
|||
|
|
FROM agent_merchants am
|
|||
|
|
JOIN users u ON am.merchant_id = u.id
|
|||
|
|
WHERE am.agent_id = ?
|
|||
|
|
GROUP BY u.audit_status
|
|||
|
|
ORDER BY count DESC`,
|
|||
|
|
[parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: statusData
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取商户状态分布数据失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取商户状态分布数据失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取代理详细统计数据(包含更多维度)
|
|||
|
|
* @route GET /agents/detailed-stats/:agent_id
|
|||
|
|
* @param {string} agent_id - 代理ID
|
|||
|
|
* @returns {Object} 详细统计数据
|
|||
|
|
*/
|
|||
|
|
router.get('/detailed-stats/:agent_id', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
|
|||
|
|
// 获取基础统计数据
|
|||
|
|
const [basicStats] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants WHERE agent_id = ?) as total_merchants,
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'approved') as approved_merchants,
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'pending') as pending_merchants,
|
|||
|
|
(SELECT COUNT(*) FROM agent_merchants am JOIN users u ON am.merchant_id = u.id WHERE am.agent_id = ? AND u.audit_status = 'rejected') as rejected_merchants,
|
|||
|
|
(SELECT COALESCE(SUM(commission_amount), 0) FROM agent_commission_records WHERE agent_id = ?) as total_commission,
|
|||
|
|
(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
|
|||
|
|
COALESCE(SUM(commission_amount), 0) as monthly_commission,
|
|||
|
|
COUNT(*) as monthly_commission_records
|
|||
|
|
FROM agent_commission_records
|
|||
|
|
WHERE agent_id = ?
|
|||
|
|
AND YEAR(created_at) = YEAR(CURDATE())
|
|||
|
|
AND MONTH(created_at) = MONTH(CURDATE())`,
|
|||
|
|
[parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取今日佣金
|
|||
|
|
const [dailyStats] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
COALESCE(SUM(commission_amount), 0) as daily_commission,
|
|||
|
|
COUNT(*) as daily_commission_records
|
|||
|
|
FROM agent_commission_records
|
|||
|
|
WHERE agent_id = ?
|
|||
|
|
AND DATE(created_at) = CURDATE()`,
|
|||
|
|
[parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取最近7天新增商户数
|
|||
|
|
const [weeklyMerchants] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as weekly_new_merchants
|
|||
|
|
FROM agent_merchants am
|
|||
|
|
JOIN users u ON am.merchant_id = u.id
|
|||
|
|
WHERE am.agent_id = ?
|
|||
|
|
AND am.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)`,
|
|||
|
|
[parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取提现相关统计数据
|
|||
|
|
const [withdrawalStats] = await getDB().execute(
|
|||
|
|
`SELECT
|
|||
|
|
CAST(COALESCE(commission_sum.total_commission, 0) AS DECIMAL(10,2)) as total_commission_calc,
|
|||
|
|
CAST(COALESCE(ra.withdrawn_amount, 0) AS DECIMAL(10,2)) as withdrawn_amount,
|
|||
|
|
CAST(COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as pending_withdrawal,
|
|||
|
|
CAST(COALESCE(commission_sum.total_commission, 0) - COALESCE(ra.withdrawn_amount, 0) - COALESCE(ra.pending_withdrawal, 0) AS DECIMAL(10,2)) as available_amount
|
|||
|
|
FROM regional_agents ra
|
|||
|
|
LEFT JOIN (
|
|||
|
|
SELECT agent_id, SUM(commission_amount) as total_commission
|
|||
|
|
FROM agent_commission_records
|
|||
|
|
WHERE agent_id = ?
|
|||
|
|
GROUP BY agent_id
|
|||
|
|
) commission_sum ON ra.id = commission_sum.agent_id
|
|||
|
|
WHERE ra.id = ?`,
|
|||
|
|
[parseInt(agent_id), parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 合并所有统计数据
|
|||
|
|
const stats = {
|
|||
|
|
...basicStats[0],
|
|||
|
|
...monthlyStats[0],
|
|||
|
|
...dailyStats[0],
|
|||
|
|
...weeklyMerchants[0],
|
|||
|
|
...(withdrawalStats[0] || {
|
|||
|
|
withdrawn_amount: 0,
|
|||
|
|
pending_withdrawal: 0,
|
|||
|
|
available_amount: 0
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: stats
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取详细统计数据失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取详细统计数据失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取代理商户的转账记录
|
|||
|
|
* @route GET /agents/merchants/:agent_id/transfers
|
|||
|
|
* @param {string} agent_id - 代理ID
|
|||
|
|
* @param {string} page - 页码
|
|||
|
|
* @param {string} limit - 每页数量
|
|||
|
|
* @returns {Object} 转账记录列表
|
|||
|
|
*/
|
|||
|
|
router.get('/merchants/:agent_id/transfers', async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { agent_id } = req.params;
|
|||
|
|
const { page = 1, limit = 10 } = req.query;
|
|||
|
|
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
|
|||
|
|
t.id,
|
|||
|
|
t.from_user_id,
|
|||
|
|
t.to_user_id,
|
|||
|
|
t.amount,
|
|||
|
|
t.status,
|
|||
|
|
t.transfer_type,
|
|||
|
|
t.description,
|
|||
|
|
t.created_at,
|
|||
|
|
t.confirmed_at,
|
|||
|
|
from_user.real_name as from_real_name,
|
|||
|
|
from_user.phone as from_phone,
|
|||
|
|
to_user.real_name as to_real_name,
|
|||
|
|
to_user.phone as to_phone
|
|||
|
|
FROM agent_merchants am
|
|||
|
|
JOIN transfers t ON am.merchant_id = t.from_user_id
|
|||
|
|
LEFT JOIN users from_user ON t.from_user_id = from_user.id
|
|||
|
|
LEFT JOIN users to_user ON t.to_user_id = to_user.id
|
|||
|
|
WHERE am.agent_id = ?
|
|||
|
|
ORDER BY t.created_at DESC
|
|||
|
|
LIMIT ${limitNum} OFFSET ${offset}
|
|||
|
|
`;
|
|||
|
|
const [transfers] = await getDB().execute(transferQuery, [parseInt(agent_id)]);
|
|||
|
|
|
|||
|
|
// 查询总数
|
|||
|
|
const [totalResult] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as total
|
|||
|
|
FROM agent_merchants am
|
|||
|
|
JOIN transfers t ON am.merchant_id = t.from_user_id
|
|||
|
|
WHERE am.agent_id = ?`,
|
|||
|
|
[parseInt(agent_id)]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const total = totalResult[0].total;
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
transfers,
|
|||
|
|
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;
|