const express = require('express'); const jianSunDb = require('../config/jianSunDatabase'); const router = express.Router(); const MAX_PAGE_SIZE = 100; function safeInt(value, fallback) { const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } function normalizeSortField(field) { const allowed = new Set([ 'id', 'shopName', 'createTime', 'activity_name', 'activity_id', 'goods_name', 'goods_id', 'min_activity_sku_price', 'max_activity_sku_price', 'activity_goods_start_time', 'activity_goods_end_time', 'status', ]); return allowed.has(field) ? field : 'activity_goods_start_time'; } function normalizeSortOrder(order) { return String(order || '').toLowerCase() === 'asc' ? 'ASC' : 'DESC'; } function escapeColumn(field) { const allowed = new Set([ 'id', 'shopName', 'createTime', 'business_type_str', 'activity_name', 'activity_id', 'goods_name', 'goods_id', 'hd_thumb_url', 'enroll_time', 'min_activity_sku_price', 'max_activity_sku_price', 'activity_goods_start_time', 'activity_goods_end_time', 'status', ]); if (!allowed.has(field)) return '[id]'; return field === 'enroll_time' ? '[enroll_time]' : `[${field}]`; } async function getSecondLatestDate() { const rows = await jianSunDb.query( `SELECT DISTINCT TOP 2 LEFT([createTime], 10) AS createDate FROM dbo.pdd_DataActivityRecords WHERE [createTime] IS NOT NULL AND LTRIM(RTRIM([createTime])) != '' ORDER BY createDate DESC` ); if (rows.length >= 2) return rows[1].createDate; if (rows.length === 1) return rows[0].createDate; return ''; } router.get('/latest-date', async (req, res) => { try { const date = await getSecondLatestDate(); res.json({ success: true, data: { date } }); } catch (error) { console.error('[MarketingEvents] 查询最新数据日期失败:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.get('/list', async (req, res) => { try { const { page = 1, pageSize = 20, sortBy = 'activity_goods_start_time', sortOrder = 'DESC', shopName, activityType, activityName, activityId, goodsName, goodsId, status, date, } = req.query; const currentPage = safeInt(page, 1); const limit = Math.min(safeInt(pageSize, 20), MAX_PAGE_SIZE); const offset = (currentPage - 1) * limit; const sortColumn = normalizeSortField(sortBy); const sortDir = normalizeSortOrder(sortOrder); const whereParts = []; const params = {}; const filterDate = (date && String(date).trim()) || await getSecondLatestDate(); if (filterDate) { whereParts.push('LEFT([createTime], 10) = @filterDate'); params.filterDate = filterDate; } if (shopName && shopName.trim()) { whereParts.push('[shopName] LIKE @shopName'); params.shopName = `%${shopName.trim()}%`; } if (activityType && activityType.trim()) { whereParts.push('[business_type_str] LIKE @activityType'); params.activityType = `%${activityType.trim()}%`; } if (activityName && activityName.trim()) { whereParts.push('[activity_name] LIKE @activityName'); params.activityName = `%${activityName.trim()}%`; } if (activityId && String(activityId).trim()) { whereParts.push('[activity_id] = @activityId'); params.activityId = Number(activityId); } if (goodsName && goodsName.trim()) { whereParts.push('[goods_name] LIKE @goodsName'); params.goodsName = `%${goodsName.trim()}%`; } if (goodsId && String(goodsId).trim()) { whereParts.push('[goods_id] = @goodsId'); params.goodsId = String(goodsId).trim(); } if (status !== undefined && status !== '' && status !== 'all') { whereParts.push('[status] = @status'); params.status = Number(status); } const whereSql = whereParts.length > 0 ? `WHERE ${whereParts.join(' AND ')}` : ''; const countResult = await jianSunDb.query( `SELECT COUNT(*) AS total FROM dbo.pdd_DataActivityRecords ${whereSql}`, params ); const total = countResult[0]?.total || 0; const dataResult = await jianSunDb.query( `SELECT [id], [shopName], [createTime], [business_type_str] AS activityType, [activity_name] AS activityName, [activity_id] AS activityId, [goods_name] AS goodsName, [goods_id] AS goodsId, [hd_thumb_url] AS thumbUrl, [enroll_time] AS enrollTime, TRY_CONVERT(decimal(18,2), [min_activity_sku_price]) / 100.0 AS minPrice, TRY_CONVERT(decimal(18,2), [max_activity_sku_price]) / 100.0 AS maxPrice, [activity_goods_start_time] AS activityStartTime, [activity_goods_end_time] AS activityEndTime, [status], CASE WHEN [status] = 501 THEN N'报名成功' ELSE CONCAT(N'状态 ', [status]) END AS statusLabel FROM dbo.pdd_DataActivityRecords ${whereSql} ORDER BY ${escapeColumn(sortColumn)} ${sortDir} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`, { ...params, offset, limit } ); const statusCountsResult = await jianSunDb.query( `SELECT [status], COUNT(*) AS count FROM dbo.pdd_DataActivityRecords ${whereSql} GROUP BY [status] ORDER BY [status]`, params ); const statusCounts = {}; let totalAll = 0; statusCountsResult.forEach((row) => { statusCounts[row.status] = row.count; totalAll += row.count; }); res.json({ success: true, data: { list: dataResult, total, page: currentPage, pageSize: limit, filterDate, statusCounts: { all: totalAll, ...statusCounts }, }, }); } catch (error) { console.error('[MarketingEvents] 查询活动记录失败:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.get('/summary', async (req, res) => { try { const { date } = req.query; const filterDate = (date && String(date).trim()) || await getSecondLatestDate(); const whereSql = filterDate ? 'WHERE LEFT([createTime], 10) = @filterDate' : ''; const params = filterDate ? { filterDate } : {}; const [row] = await jianSunDb.query( `SELECT COUNT(*) AS totalRecords, SUM(CASE WHEN [status] = 501 THEN 1 ELSE 0 END) AS successCount, COUNT(DISTINCT TRY_CONVERT(varchar(50), [goods_id])) AS goodsCount, COUNT(DISTINCT [shopName]) AS shopCount, COUNT(DISTINCT [activity_id]) AS activityIdCount, COUNT(DISTINCT [business_type_str]) AS activityTypeCount, AVG(TRY_CONVERT(decimal(18,2), [min_activity_sku_price])) / 100.0 AS avgMinPrice, AVG(TRY_CONVERT(decimal(18,2), [max_activity_sku_price])) / 100.0 AS avgMaxPrice, MIN(TRY_CONVERT(decimal(18,2), [min_activity_sku_price])) / 100.0 AS globalMinPrice, MAX(TRY_CONVERT(decimal(18,2), [max_activity_sku_price])) / 100.0 AS globalMaxPrice FROM dbo.pdd_DataActivityRecords ${whereSql}`, params ); const stats = row || {}; const totalRecords = Number(stats.totalRecords) || 0; const successCount = Number(stats.successCount) || 0; const successRate = totalRecords > 0 ? ((successCount / totalRecords) * 100).toFixed(1) : '0.0'; res.json({ success: true, data: { filterDate, totalRecords, successCount, successRate, goodsCount: Number(stats.goodsCount) || 0, shopCount: Number(stats.shopCount) || 0, activityIdCount: Number(stats.activityIdCount) || 0, activityTypeCount: Number(stats.activityTypeCount) || 0, avgMinPrice: stats.avgMinPrice != null ? Number(stats.avgMinPrice).toFixed(2) : '-', avgMaxPrice: stats.avgMaxPrice != null ? Number(stats.avgMaxPrice).toFixed(2) : '-', globalMinPrice: stats.globalMinPrice != null ? Number(stats.globalMinPrice).toFixed(2) : '-', globalMaxPrice: stats.globalMaxPrice != null ? Number(stats.globalMaxPrice).toFixed(2) : '-', }, }); } catch (error) { console.error('[MarketingEvents] 查询汇总数据失败:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.get('/status-counts', async (req, res) => { try { const result = await jianSunDb.query( `SELECT [status], COUNT(*) AS count FROM dbo.pdd_DataActivityRecords GROUP BY [status] ORDER BY [status]` ); const statusCounts = {}; let total = 0; result.forEach((row) => { statusCounts[row.status] = row.count; total += row.count; }); res.json({ success: true, data: { all: total, ...statusCounts }, }); } catch (error) { console.error('[MarketingEvents] 查询状态统计失败:', error.message); res.status(500).json({ success: false, error: error.message }); } }); module.exports = router;