415 lines
13 KiB
JavaScript
415 lines
13 KiB
JavaScript
const express = require('express');
|
|
const fs = require('fs');
|
|
const authMiddleware = require('../middleware/auth');
|
|
const {
|
|
getChannelList,
|
|
exportChannelListCsv,
|
|
createChannelExportJob,
|
|
getChannelExportJob,
|
|
getChannelExportJobFile,
|
|
getChannelDashboard,
|
|
getChannelIdAnalysisList,
|
|
getChannelIdAnalysisDashboard,
|
|
getChannelSpuList,
|
|
getChannelStockMonitorDashboard,
|
|
getChannelDetail,
|
|
getChannelLargeCustomerList,
|
|
getChannelLargeCustomerDashboard,
|
|
getChannelLargeCustomerDetail,
|
|
refreshChannelLargeCustomerReadStore,
|
|
} = require('../services/channelAnalysis');
|
|
|
|
const router = express.Router();
|
|
|
|
function parseRangeHeader(rangeHeader, totalSize) {
|
|
if (!rangeHeader) return null;
|
|
|
|
const match = String(rangeHeader).match(/^bytes=(\d*)-(\d*)$/);
|
|
if (!match) return { invalid: true };
|
|
|
|
let start = match[1] === '' ? null : Number.parseInt(match[1], 10);
|
|
let end = match[2] === '' ? null : Number.parseInt(match[2], 10);
|
|
|
|
if (start === null && end === null) return { invalid: true };
|
|
if (start === null) {
|
|
const suffixLength = end;
|
|
if (!Number.isFinite(suffixLength) || suffixLength <= 0) return { invalid: true };
|
|
start = Math.max(totalSize - suffixLength, 0);
|
|
end = totalSize - 1;
|
|
} else {
|
|
if (!Number.isFinite(start) || start < 0) return { invalid: true };
|
|
end = end === null ? totalSize - 1 : end;
|
|
}
|
|
|
|
if (!Number.isFinite(end) || end < start || start >= totalSize) {
|
|
return { invalid: true };
|
|
}
|
|
|
|
return {
|
|
start,
|
|
end: Math.min(end, totalSize - 1),
|
|
};
|
|
}
|
|
|
|
function setCsvDownloadHeaders(res, { filename, totalSize, contentLength, range }) {
|
|
const encodedFilename = encodeURIComponent(filename || `channel-data-${Date.now()}.csv`);
|
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFilename}`);
|
|
res.setHeader('Accept-Ranges', 'bytes');
|
|
res.setHeader('Content-Length', String(contentLength));
|
|
res.setHeader('Cache-Control', 'private, no-store');
|
|
if (range) {
|
|
res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${totalSize}`);
|
|
}
|
|
}
|
|
|
|
router.get('/list', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelList({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
page: req.query.page,
|
|
pageSize: req.query.pageSize,
|
|
keyword: req.query.keyword,
|
|
sortField: req.query.sortField,
|
|
sortOrder: req.query.sortOrder,
|
|
channels: req.query.channels,
|
|
statuses: req.query.statuses,
|
|
purchasers: req.query.purchasers,
|
|
minAfterSaleAmountRate: req.query.minAfterSaleAmountRate,
|
|
maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道数据失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道数据失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/export', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await exportChannelListCsv({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
keyword: req.query.keyword,
|
|
sortField: req.query.sortField,
|
|
sortOrder: req.query.sortOrder,
|
|
channels: req.query.channels,
|
|
statuses: req.query.statuses,
|
|
purchasers: req.query.purchasers,
|
|
minAfterSaleAmountRate: req.query.minAfterSaleAmountRate,
|
|
maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate,
|
|
});
|
|
|
|
const encodedFilename = encodeURIComponent(data.filename || `channel-data-${Date.now()}.csv`);
|
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFilename}`);
|
|
res.setHeader('X-Total-Count', String(data.total || 0));
|
|
res.setHeader('X-Export-Mode', data.exportMode || 'exact-enriched');
|
|
res.send(data.csv);
|
|
} catch (error) {
|
|
console.error('导出渠道数据失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '导出渠道数据失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.post('/export-jobs', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = createChannelExportJob({
|
|
startDate: req.body.startDate,
|
|
endDate: req.body.endDate,
|
|
keyword: req.body.keyword,
|
|
sortField: req.body.sortField,
|
|
sortOrder: req.body.sortOrder,
|
|
channels: req.body.channels,
|
|
statuses: req.body.statuses,
|
|
purchasers: req.body.purchasers,
|
|
minAfterSaleAmountRate: req.body.minAfterSaleAmountRate,
|
|
maxAfterSaleAmountRate: req.body.maxAfterSaleAmountRate,
|
|
}, req.user);
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('创建渠道数据导出任务失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '创建渠道数据导出任务失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/export-jobs/:jobId', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelExportJob(req.params.jobId);
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道数据导出任务失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/export-jobs/:jobId/download', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { filePath, filename } = await getChannelExportJobFile(req.params.jobId);
|
|
const stat = await fs.promises.stat(filePath);
|
|
const totalSize = stat.size;
|
|
const range = parseRangeHeader(req.headers.range, totalSize);
|
|
|
|
if (range?.invalid) {
|
|
res.status(416).setHeader('Content-Range', `bytes */${totalSize}`);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
if (range) {
|
|
const contentLength = range.end - range.start + 1;
|
|
setCsvDownloadHeaders(res, { filename, totalSize, contentLength, range });
|
|
res.status(206);
|
|
fs.createReadStream(filePath, { start: range.start, end: range.end }).pipe(res);
|
|
return;
|
|
}
|
|
|
|
setCsvDownloadHeaders(res, { filename, totalSize, contentLength: totalSize });
|
|
fs.createReadStream(filePath).pipe(res);
|
|
} catch (error) {
|
|
console.error('下载渠道数据导出文件失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '下载渠道数据导出文件失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/dashboard', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelDashboard({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
keyword: req.query.keyword,
|
|
limit: req.query.limit,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道多维分析仪表盘失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道多维分析仪表盘失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/spu-list', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelSpuList({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
page: req.query.page,
|
|
pageSize: req.query.pageSize,
|
|
keyword: req.query.keyword,
|
|
sortField: req.query.sortField,
|
|
sortOrder: req.query.sortOrder,
|
|
statuses: req.query.statuses,
|
|
purchasers: req.query.purchasers,
|
|
minAfterSaleAmountRate: req.query.minAfterSaleAmountRate,
|
|
maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道SPU图片数据失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道SPU图片数据失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/stock-monitor-dashboard', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelStockMonitorDashboard({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
keyword: req.query.keyword,
|
|
limit: req.query.limit,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道库存影响仪表盘失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道库存影响仪表盘失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/id-analysis-dashboard', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelIdAnalysisDashboard({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
keyword: req.query.keyword,
|
|
limit: req.query.limit,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道ID多维分析仪表盘失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道ID多维分析仪表盘失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/id-analysis', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelIdAnalysisList({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
page: req.query.page,
|
|
pageSize: req.query.pageSize,
|
|
keyword: req.query.keyword,
|
|
sortField: req.query.sortField,
|
|
sortOrder: req.query.sortOrder,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道ID分析失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道ID分析失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/detail', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelDetail({
|
|
startDate: req.query.startDate,
|
|
endDate: req.query.endDate,
|
|
channelName: req.query.channelName,
|
|
shopName: req.query.shopName,
|
|
skuCode: req.query.skuCode,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道明细失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道明细失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/large-customers', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelLargeCustomerList({
|
|
startMonth: req.query.startMonth,
|
|
endMonth: req.query.endMonth,
|
|
page: req.query.page,
|
|
pageSize: req.query.pageSize,
|
|
keyword: req.query.keyword,
|
|
sortField: req.query.sortField,
|
|
sortOrder: req.query.sortOrder,
|
|
minSingleOrderQty: req.query.minSingleOrderQty,
|
|
autoSync: req.query.autoSync,
|
|
channels: req.query.channels,
|
|
shops: req.query.shops,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道大客户分析失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道大客户分析失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/large-customers/dashboard', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelLargeCustomerDashboard({
|
|
startMonth: req.query.startMonth,
|
|
endMonth: req.query.endMonth,
|
|
keyword: req.query.keyword,
|
|
minSingleOrderQty: req.query.minSingleOrderQty,
|
|
channels: req.query.channels,
|
|
shops: req.query.shops,
|
|
limit: req.query.limit,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道大客户多维分析仪表盘失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道大客户多维分析仪表盘失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/large-customers/detail', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await getChannelLargeCustomerDetail({
|
|
startMonth: req.query.startMonth,
|
|
endMonth: req.query.endMonth,
|
|
buyerId: req.query.buyerId,
|
|
channelName: req.query.channelName,
|
|
shopName: req.query.shopName,
|
|
channels: req.query.channels,
|
|
shops: req.query.shops,
|
|
minSingleOrderQty: req.query.minSingleOrderQty,
|
|
limit: req.query.limit,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('获取渠道大客户订单明细失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '获取渠道大客户订单明细失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
router.post('/large-customers/refresh', authMiddleware, async (req, res) => {
|
|
try {
|
|
const data = await refreshChannelLargeCustomerReadStore({
|
|
startMonth: req.body.startMonth,
|
|
endMonth: req.body.endMonth,
|
|
page: req.body.page,
|
|
pageSize: req.body.pageSize,
|
|
keyword: req.body.keyword,
|
|
sortField: req.body.sortField,
|
|
sortOrder: req.body.sortOrder,
|
|
minSingleOrderQty: req.body.minSingleOrderQty,
|
|
channels: req.body.channels,
|
|
shops: req.body.shops,
|
|
});
|
|
|
|
res.json({ success: true, data });
|
|
} catch (error) {
|
|
console.error('刷新渠道大客户分析失败:', error);
|
|
res.status(error.statusCode || 500).json({
|
|
success: false,
|
|
error: error.message || '刷新渠道大客户分析失败',
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|