Commit 0c953e5c authored by 刘小敏's avatar 刘小敏

数据中心优化;计划任务日志优化;

parent fb3982b7
...@@ -37,100 +37,77 @@ class Dashboard extends Common ...@@ -37,100 +37,77 @@ class Dashboard extends Common
return $this->view->fetch(); return $this->view->fetch();
} }
/** private function buildStatisticsResult($statsData, $period, $fromCache, $updateTime)
* 获取核心统计数据
*
* 该接口优先从缓存读取 DataStatsCenter 定时任务生成的统计数据,
* 缓存未命中时降级到数据库实时查询。
*
* 请求参数:
* - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days'
* - period: 周期类型,可选值:'day'、'week'、'month',默认 'day'
*
* 返回数据:
* - total_amount: 成交总额(已支付和已完成订单的支付金额总和)
* - profit_rate: 净利润率(百分比格式,如 '18.6%')
* - avg_amount: 客单价(成交总额 / 订单数)
* - order_count: 订单数(已支付和已完成订单数量)
* - visitor_count: 访客数(基于小程序行为埋点统计的去重用户数)
* - conversion_rate: 转化率(订单数 / 访客数,百分比格式)
* - register_users: 注册用户数(累计注册用户总数)
* - trend_data: 趋势数据数组,每个元素包含:
* - date: 日期标签
* - amount: 该日期的成交额
* - order_num: 该日期的订单数
* - register_num: 该日期的注册用户数
* - from_cache: 数据来源标识,true 表示来自缓存,false 表示来自实时查询
*/
public function statistics()
{ {
$timeRange = $this->request->param('time_range', '30days'); $summary = $statsData['summary'];
$period = $this->request->param('period', 'day'); $trendData = $this->buildTrendDataForChart($statsData['trend'], $period);
$funnelData = $this->formatFunnelData($statsData['funnel'] ?? []);
$cacheKey = 'shopro:data_stats_center:' . $timeRange; $priceDistributionData = $this->formatPriceDistributionData($statsData['price_distribution'] ?? []);
$cachedData = cache($cacheKey);
// 获取最新统计记录的更新时间 return [
$updateTime = Db::name('shopro_data_stats_center') 'total_amount' => $summary['total_amount']['value'] ?? 0,
->order('created_at', 'desc') 'total_amount_compare_rate' => $summary['total_amount']['compare_rate'] ?? null,
->value('created_at'); 'profit_rate' => $summary['profit_rate']['formatted'] ?? '0.00%',
'profit_rate_compare_rate' => $summary['profit_rate']['compare_rate'] ?? null,
'avg_amount' => $summary['avg_amount']['value'] ?? 0,
'avg_amount_compare_rate' => $summary['avg_amount']['compare_rate'] ?? null,
'order_count' => $summary['order_count']['value'] ?? 0,
'order_count_compare_rate' => $summary['order_count']['compare_rate'] ?? null,
'visitor_count' => $summary['visitor_count']['value'] ?? 0,
'visitor_count_compare_rate' => $summary['visitor_count']['compare_rate'] ?? null,
'conversion_rate' => $summary['conversion_rate']['formatted'] ?? '0.00%',
'conversion_rate_compare_rate' => $summary['conversion_rate']['compare_rate'] ?? null,
'register_users' => $summary['register_users']['value'] ?? 0,
'register_users_compare_rate' => $summary['register_users']['compare_rate'] ?? null,
'trend_data' => $trendData,
'funnel_data' => $funnelData,
'price_distribution_data' => $priceDistributionData,
'from_cache' => $fromCache,
'update_time' => $updateTime ? date('Y-m-d H:i:s', $updateTime) : date('Y-m-d H:i:s')
];
}
if ($cachedData !== false && isset($cachedData['summary']) && isset($cachedData['trend'])) { private function getEmptyStatisticsResult()
$summary = $cachedData['summary']; {
$trendData = $this->buildTrendDataForChart($cachedData['trend'], $period); return [
'total_amount' => 0,
// 较上周期 效果计算 'total_amount_compare_rate' => null,
// - 上一周期和当前周期数据都是0时 → 显示"持平" 'profit_rate' => '0.00%',
// - 上一周期是0但当前周期有数据 → 显示"+100%" 'profit_rate_compare_rate' => null,
// - 上一周期有数据但当前周期是0 → 显示"-100%" 'avg_amount' => 0,
// - 两个周期都有数据时 → 显示实际百分比 'avg_amount_compare_rate' => null,
$result = [ 'order_count' => 0,
'total_amount' => $summary['total_amount']['value'], 'order_count_compare_rate' => null,
'total_amount_compare_rate' => $summary['total_amount']['compare_rate'], 'visitor_count' => 0,
'profit_rate' => $summary['profit_rate']['formatted'], 'visitor_count_compare_rate' => null,
'profit_rate_compare_rate' => $summary['profit_rate']['compare_rate'], 'conversion_rate' => '0.00%',
'avg_amount' => $summary['avg_amount']['value'], 'conversion_rate_compare_rate' => null,
'avg_amount_compare_rate' => $summary['avg_amount']['compare_rate'], 'register_users' => 0,
'order_count' => $summary['order_count']['value'], 'register_users_compare_rate' => null,
'order_count_compare_rate' => $summary['order_count']['compare_rate'], 'trend_data' => [],
'visitor_count' => $summary['visitor_count']['value'], 'funnel_data' => [],
'visitor_count_compare_rate' => $summary['visitor_count']['compare_rate'], 'price_distribution_data' => [],
'conversion_rate' => $summary['conversion_rate']['formatted'], 'from_cache' => false,
'conversion_rate_compare_rate' => $summary['conversion_rate']['compare_rate'], 'update_time' => date('Y-m-d H:i:s')
'register_users' => $summary['register_users']['value'], ];
'register_users_compare_rate' => $summary['register_users']['compare_rate'], }
'trend_data' => $trendData,
'from_cache' => true,
'update_time' => $updateTime ? date('Y-m-d H:i:s', $updateTime) : date('Y-m-d H:i:s')
];
$this->success('获取成功', null, $result); private function formatFunnelData($funnel)
{
$result = [];
foreach ($funnel as $item) {
$result[$item['key']] = $item['value'];
} }
return $result;
}
// $days = $this->getDaysByTimeRange($timeRange); private function formatPriceDistributionData($distribution)
// $trendData = $this->getTrendData($days, $period); {
$result = [];
// $result = [ foreach ($distribution as $item) {
// 'total_amount' => $trendData['total_amount'], $result[$item['label']] = $item['rate'];
// 'total_amount_compare_rate' => $trendData['total_amount_compare_rate'] ?? 0, }
// 'profit_rate' => $trendData['profit_rate'], return $result;
// 'profit_rate_compare_rate' => $trendData['profit_rate_compare_rate'] ?? 0,
// 'avg_amount' => $trendData['avg_amount'],
// 'avg_amount_compare_rate' => $trendData['avg_amount_compare_rate'] ?? 0,
// 'order_count' => $trendData['order_count'],
// 'order_count_compare_rate' => $trendData['order_count_compare_rate'] ?? 0,
// 'visitor_count' => $trendData['visitor_count'],
// 'visitor_count_compare_rate' => $trendData['visitor_count_compare_rate'] ?? 0,
// 'conversion_rate' => $trendData['conversion_rate'],
// 'conversion_rate_compare_rate' => $trendData['conversion_rate_compare_rate'] ?? 0,
// 'register_users' => $trendData['register_users'],
// 'register_users_compare_rate' => $trendData['register_users_compare_rate'] ?? 0,
// 'trend_data' => $trendData['trend_data'],
// 'from_cache' => false,
// 'update_time' => $updateTime ? date('Y-m-d H:i:s', $updateTime) : date('Y-m-d H:i:s')
// ];
// $this->success('获取成功', null, $result);
} }
/** /**
...@@ -298,137 +275,18 @@ class Dashboard extends Common ...@@ -298,137 +275,18 @@ class Dashboard extends Common
* @param string $timeRange 时间范围标识 * @param string $timeRange 时间范围标识
* @return int 对应的天数 * @return int 对应的天数
*/ */
private function getDaysByTimeRange($timeRange) // private function getDaysByTimeRange($timeRange)
{ // {
switch ($timeRange) { // switch ($timeRange) {
case '7days': // case '7days':
return 7; // return 7;
case '90days': // case '90days':
return 90; // return 90;
case '30days': // case '30days':
default: // default:
return 30; // return 30;
} // }
} // }
/**
* 获取趋势数据(降级方案)
*
* 当缓存未命中时,直接从数据库查询并计算趋势数据
*
* @param int $days 统计天数
* @param string $period 周期类型
* @return array 包含各项指标和趋势数据的数组
*/
private function getTrendData($days, $period)
{
$data = [];
$totalAmount = 0;
$orderCount = 0;
$registerUsers = 0;
$visitorCount = 0;
$buyerCount = 0;
$format = $this->getDateFormat($period);
for ($i = $days - 1; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$startTime = strtotime($date . ' 00:00:00');
$endTime = strtotime($date . ' 23:59:59');
$amount = Order::whereBetween('createtime', [$startTime, $endTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->sum('pay_fee') ?: 0;
$orderNum = Order::whereBetween('createtime', [$startTime, $endTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->count();
$buyerNum = Order::whereBetween('createtime', [$startTime, $endTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->where('user_id', '>', 0)
->distinct('user_id')
->count('user_id');
$registerNum = User::whereBetween('createtime', [$startTime, $endTime])->count();
try {
$visitorNum = $this->getVisitorCountFromTrack($startTime, $endTime);
} catch (\Exception $e) {
$visitorNum = 0;
}
$data[] = [
'date' => date($format, $startTime),
'amount' => $amount,
'order_num' => $orderNum,
'register_num' => $registerNum
];
$totalAmount += $amount;
$orderCount += $orderNum;
$registerUsers += $registerNum;
$visitorCount += $visitorNum;
$buyerCount += $buyerNum;
}
$previousStartTime = $startTime - ($days * 86400);
$previousEndTime = $startTime - 1;
$previousAmount = Order::whereBetween('createtime', [$previousStartTime, $previousEndTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->sum('pay_fee') ?: 0;
$previousOrderNum = Order::whereBetween('createtime', [$previousStartTime, $previousEndTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->count();
$previousBuyerNum = Order::whereBetween('createtime', [$previousStartTime, $previousEndTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->where('user_id', '>', 0)
->distinct('user_id')
->count('user_id');
$previousRegisterNum = User::whereBetween('createtime', [$previousStartTime, $previousEndTime])->count();
try {
$previousVisitorNum = $this->getVisitorCountFromTrack($previousStartTime, $previousEndTime);
} catch (\Exception $e) {
$previousVisitorNum = 0;
}
$previousAvgAmount = $previousOrderNum > 0 ? bcdiv($previousAmount, $previousOrderNum, 2) : 0;
$previousConversionRate = $previousVisitorNum > 0 ? bcdiv($previousBuyerNum * 100, $previousVisitorNum, 2) : 0;
$avgAmount = $orderCount > 0 ? bcdiv($totalAmount, $orderCount, 2) : 0;
$profitRate = $totalAmount > 0 ? '18.6%' : '0%';
$conversionRate = $visitorCount > 0 ? bcdiv($buyerCount * 100, $visitorCount, 2) . '%' : '0%';
$totalAmountCompareRate = $previousAmount > 0 ? round((($totalAmount - $previousAmount) / $previousAmount) * 100, 2) : ($totalAmount > 0 ? 100 : ($previousAmount == 0 ? null : -100));
$avgAmountCompareRate = $previousAvgAmount > 0 ? round((($avgAmount - $previousAvgAmount) / $previousAvgAmount) * 100, 2) : ($avgAmount > 0 ? 100 : ($previousAvgAmount == 0 ? null : -100));
$orderCountCompareRate = $previousOrderNum > 0 ? round((($orderCount - $previousOrderNum) / $previousOrderNum) * 100, 2) : ($orderCount > 0 ? 100 : ($previousOrderNum == 0 ? null : -100));
$visitorCountCompareRate = $previousVisitorNum > 0 ? round((($visitorCount - $previousVisitorNum) / $previousVisitorNum) * 100, 2) : ($visitorCount > 0 ? 100 : ($previousVisitorNum == 0 ? null : -100));
$conversionRateCompareRate = $previousConversionRate > 0 ? round((($conversionRate - $previousConversionRate) / $previousConversionRate) * 100, 2) : ($conversionRate > 0 ? 100 : ($previousConversionRate == 0 ? null : -100));
$registerUsersCompareRate = $previousRegisterNum > 0 ? round((($registerUsers - $previousRegisterNum) / $previousRegisterNum) * 100, 2) : ($registerUsers > 0 ? 100 : ($previousRegisterNum == 0 ? null : -100));
return [
'total_amount' => $totalAmount,
'total_amount_compare_rate' => $totalAmountCompareRate,
'order_count' => $orderCount,
'order_count_compare_rate' => $orderCountCompareRate,
'avg_amount' => $avgAmount,
'avg_amount_compare_rate' => $avgAmountCompareRate,
'profit_rate' => $profitRate,
'profit_rate_compare_rate' => 0,
'visitor_count' => $visitorCount,
'visitor_count_compare_rate' => $visitorCountCompareRate,
'conversion_rate' => $conversionRate,
'conversion_rate_compare_rate' => $conversionRateCompareRate,
'register_users' => $registerUsers,
'register_users_compare_rate' => $registerUsersCompareRate,
'trend_data' => $data
];
}
/** /**
* 获取日期格式化字符串 * 获取日期格式化字符串
...@@ -436,18 +294,18 @@ class Dashboard extends Common ...@@ -436,18 +294,18 @@ class Dashboard extends Common
* @param string $period 周期类型 * @param string $period 周期类型
* @return string PHP 日期格式化字符串 * @return string PHP 日期格式化字符串
*/ */
private function getDateFormat($period) // private function getDateFormat($period)
{ // {
switch ($period) { // switch ($period) {
case 'week': // case 'week':
return 'Y年W周'; // return 'Y年W周';
case 'month': // case 'month':
return 'Y年m月'; // return 'Y年m月';
case 'day': // case 'day':
default: // default:
return 'm-d'; // return 'm-d';
} // }
} // }
/** /**
* 获取转化漏斗数据 * 获取转化漏斗数据
...@@ -465,147 +323,276 @@ class Dashboard extends Common ...@@ -465,147 +323,276 @@ class Dashboard extends Common
* - submit_order: 提交订单人数(下单的去重用户数) * - submit_order: 提交订单人数(下单的去重用户数)
* - pay: 完成支付人数(已支付或已完成订单的去重用户数) * - pay: 完成支付人数(已支付或已完成订单的去重用户数)
*/ */
public function funnel() // public function funnel()
{ // {
$timeRange = $this->request->param('time_range', '30days'); // $timeRange = $this->request->param('time_range', '30days');
// $cacheKey = 'shopro:data_stats_center:' . $timeRange;
// $cachedData = cache($cacheKey);
// if ($cachedData !== false && isset($cachedData['funnel'])) {
// $funnel = $cachedData['funnel'];
// $result = [];
// foreach ($funnel as $item) {
// $result[$item['key']] = $item['value'];
// }
// $this->success('获取成功', null, $result);
// }
// $days = $this->getDaysByTimeRange($timeRange);
// $endTime = time();
// $startTime = strtotime("-$days days");
// $visitCount = 0;
// try {
// $visitCount = Db::name('shopro_user_goods_log')
// ->whereBetween('createtime', [$startTime, $endTime])
// ->distinct(true)
// ->count('user_id');
// } catch (\Exception $e) {
// $visitCount = 0;
// }
// $browseCount = 0;
// try {
// $browseCount = Db::name('shopro_user_goods_log')
// ->whereBetween('createtime', [$startTime, $endTime])
// ->where('type', 'browse')
// ->distinct(true)
// ->count('user_id');
// } catch (\Exception $e) {
// $browseCount = 0;
// }
// $cartCount = 0;
// try {
// $cartCount = Db::name('shopro_user_goods_log')
// ->whereBetween('createtime', [$startTime, $endTime])
// ->where('type', 'cart')
// ->distinct(true)
// ->count('user_id');
// } catch (\Exception $e) {
// $cartCount = 0;
// }
// $submitOrderCount = Order::whereBetween('createtime', [$startTime, $endTime])
// ->distinct(true)
// ->count('user_id');
// $payCount = Order::whereBetween('createtime', [$startTime, $endTime])
// ->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
// ->distinct(true)
// ->count('user_id');
// $this->success('获取成功', null, [
// 'visit' => $visitCount,
// 'browse' => $browseCount,
// 'cart' => $cartCount,
// 'submit_order' => $submitOrderCount,
// 'pay' => $payCount
// ]);
// }
$cacheKey = 'shopro:data_stats_center:' . $timeRange; /**
$cachedData = cache($cacheKey); * 获取客单价分布
*
if ($cachedData !== false && isset($cachedData['funnel'])) { * 统计不同价格区间的订单占比
$funnel = $cachedData['funnel']; * 优先从缓存读取,缓存未命中时降级到数据库查询
$result = []; *
foreach ($funnel as $item) { * 请求参数:
$result[$item['key']] = $item['value']; * - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days'
} *
$this->success('获取成功', null, $result); * 返回数据:
} * - '0-100': 0-100元订单占比(百分比)
* - '100-300': 100-300元订单占比(百分比)
* - '300-500': 300-500元订单占比(百分比)
* - '500-1000': 500-1000元订单占比(百分比)
* - '1000+': 1000元以上订单占比(百分比)
*/
// public function priceDistribution()
// {
// $timeRange = $this->request->param('time_range', '30days');
// $cacheKey = 'shopro:data_stats_center:' . $timeRange;
// $cachedData = cache($cacheKey);
// if ($cachedData !== false && isset($cachedData['price_distribution'])) {
// $distribution = $cachedData['price_distribution'];
// $result = [];
// foreach ($distribution as $item) {
// $result[$item['label']] = $item['rate'];
// }
// $this->success('获取成功', null, $result);
// }
// $days = $this->getDaysByTimeRange($timeRange);
// $endTime = time();
// $startTime = strtotime("-$days days");
// $orders = Order::whereBetween('createtime', [$startTime, $endTime])
// ->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
// ->field('pay_fee')
// ->select();
// $distribution = [
// '0-100' => 0,
// '100-300' => 0,
// '300-500' => 0,
// '500-1000' => 0,
// '1000+' => 0
// ];
// $total = count($orders);
// foreach ($orders as $order) {
// $amount = $order['pay_fee'];
// if ($amount < 100) {
// $distribution['0-100']++;
// } elseif ($amount < 300) {
// $distribution['100-300']++;
// } elseif ($amount < 500) {
// $distribution['300-500']++;
// } elseif ($amount < 1000) {
// $distribution['500-1000']++;
// } else {
// $distribution['1000+']++;
// }
// }
// foreach ($distribution as &$val) {
// $val = $total > 0 ? round($val / $total * 100) : 0;
// }
// $this->success('获取成功', null, $distribution);
// }
$days = $this->getDaysByTimeRange($timeRange); /**
$endTime = time(); * 格式化时间为相对时间
$startTime = strtotime("-$days days"); *
* @param mixed $timestamp 时间戳或日期字符串
$visitCount = 0; * @return string 格式化后的时间字符串
try { */
$visitCount = Db::name('shopro_user_goods_log') private function formatTime($timestamp)
->whereBetween('createtime', [$startTime, $endTime]) {
->distinct(true) if (empty($timestamp)) {
->count('user_id'); return '未知时间';
} catch (\Exception $e) {
$visitCount = 0;
} }
$browseCount = 0; // 如果不是数字,尝试转换为时间戳
try { if (!is_numeric($timestamp)) {
$browseCount = Db::name('shopro_user_goods_log') $timestamp = strtotime($timestamp);
->whereBetween('createtime', [$startTime, $endTime]) if ($timestamp === false) {
->where('type', 'browse') return '未知时间';
->distinct(true) }
->count('user_id');
} catch (\Exception $e) {
$browseCount = 0;
} }
$cartCount = 0; $timestamp = (int)$timestamp;
try {
$cartCount = Db::name('shopro_user_goods_log') // 验证时间戳是否在合理范围内(1970年到2100年)
->whereBetween('createtime', [$startTime, $endTime]) if ($timestamp < 0 || $timestamp > 4102444800) {
->where('type', 'cart') return '未知时间';
->distinct(true)
->count('user_id');
} catch (\Exception $e) {
$cartCount = 0;
} }
$submitOrderCount = Order::whereBetween('createtime', [$startTime, $endTime]) $now = time();
->distinct(true) $diff = $now - $timestamp;
->count('user_id');
$payCount = Order::whereBetween('createtime', [$startTime, $endTime])
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED])
->distinct(true)
->count('user_id');
$this->success('获取成功', null, [ if ($diff < 60) {
'visit' => $visitCount, return '刚刚';
'browse' => $browseCount, } elseif ($diff < 3600) {
'cart' => $cartCount, return floor($diff / 60) . '分钟前';
'submit_order' => $submitOrderCount, } elseif ($diff < 86400) {
'pay' => $payCount return floor($diff / 3600) . '小时前';
]); } else {
return date('m-d H:i', $timestamp);
}
} }
/** /**
* 获取客单价分布 * 从埋点表获取访客数
*
* 使用与定时任务相同的逻辑统计去重访客数
* 用户识别优先级:user_id > session_id > ip
*
* @param int $startTime 开始时间戳
* @param int $endTime 结束时间戳
* @return int 去重后的访客数量
*/
// private function getVisitorCountFromTrack($startTime, $endTime)
// {
// $table = config('database.prefix') . 'shopro_miniprogram_track';
// $sql = "SELECT COUNT(DISTINCT CASE
// WHEN user_id > 0 THEN CONCAT('u_', user_id)
// WHEN session_id <> '' THEN CONCAT('s_', session_id)
// ELSE CONCAT('i_', ip)
// END) AS total
// FROM `{$table}`
// WHERE create_time BETWEEN ? AND ?";
// $result = Db::query($sql, [$startTime, $endTime]);
// return isset($result[0]['total']) ? (int)$result[0]['total'] : 0;
// }
/**
* 获取核心统计数据
* *
* 统计不同价格区间的订单占比 * 该接口优先从缓存读取 DataStatsCenter 定时任务生成的统计数据,
* 优先从缓存读取,缓存未命中时降级到数据库查询 * 缓存未命中时降级到数据库实时查询。
* *
* 请求参数: * 请求参数:
* - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days' * - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days'
* - period: 周期类型,可选值:'day'、'week'、'month',默认 'day'
* *
* 返回数据: * 返回数据:
* - '0-100': 0-100元订单占比(百分比) * - total_amount: 成交总额(已支付和已完成订单的支付金额总和)
* - '100-300': 100-300元订单占比(百分比) * - profit_rate: 净利润率(百分比格式,如 '18.6%')
* - '300-500': 300-500元订单占比(百分比) * - avg_amount: 客单价(成交总额 / 订单数)
* - '500-1000': 500-1000元订单占比(百分比) * - order_count: 订单数(已支付和已完成订单数量)
* - '1000+': 1000元以上订单占比(百分比) * - visitor_count: 访客数(基于小程序行为埋点统计的去重用户数)
* - conversion_rate: 转化率(订单数 / 访客数,百分比格式)
* - register_users: 注册用户数(累计注册用户总数)
* - trend_data: 趋势数据数组,每个元素包含:
* - date: 日期标签
* - amount: 该日期的成交额
* - order_num: 该日期的订单数
* - register_num: 该日期的注册用户数
* - from_cache: 数据来源标识,true 表示来自缓存,false 表示来自实时查询
*/ */
public function priceDistribution() public function statistics()
{ {
$timeRange = $this->request->param('time_range', '30days'); $timeRange = $this->request->param('time_range', '30days');
$period = $this->request->param('period', 'day');
$cacheKey = 'shopro:data_stats_center:' . $timeRange; $cacheKey = 'shopro:data_stats_center:' . $timeRange;
$cachedData = cache($cacheKey); $cachedData = cache($cacheKey);
if ($cachedData !== false && isset($cachedData['price_distribution'])) { if ($cachedData !== false && isset($cachedData['summary']) && isset($cachedData['trend'])) {
$distribution = $cachedData['price_distribution']; $updateTime = Db::name('shopro_data_stats_center')
$result = []; ->order('updated_at', 'desc')
foreach ($distribution as $item) { ->value('updated_at');
$result[$item['label']] = $item['rate']; $result = $this->buildStatisticsResult($cachedData, $period, true, $updateTime);
}
$this->success('获取成功', null, $result); $this->success('获取成功', null, $result);
} }
$days = $this->getDaysByTimeRange($timeRange); $latestRecord = Db::name('shopro_data_stats_center')
$endTime = time(); ->order('created_at', 'desc')
$startTime = strtotime("-$days days"); ->find();
$orders = Order::whereBetween('createtime', [$startTime, $endTime]) if ($latestRecord) {
->whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED]) $statsData = [
->field('pay_fee') 'summary' => json_decode($latestRecord['summary_json'], true) ?: [],
->select(); 'trend' => json_decode($latestRecord['trend_json'], true) ?: [],
'funnel' => json_decode($latestRecord['funnel_json'], true) ?: [],
$distribution = [ 'price_distribution' => json_decode($latestRecord['distribution_json'], true) ?: []
'0-100' => 0, ];
'100-300' => 0, cache($cacheKey, $statsData, 86400);
'300-500' => 0, $result = $this->buildStatisticsResult($statsData, $period, false, $latestRecord['created_at']);
'500-1000' => 0, $this->success('获取成功', null, $result);
'1000+' => 0
];
$total = count($orders);
foreach ($orders as $order) {
$amount = $order['pay_fee'];
if ($amount < 100) {
$distribution['0-100']++;
} elseif ($amount < 300) {
$distribution['100-300']++;
} elseif ($amount < 500) {
$distribution['300-500']++;
} elseif ($amount < 1000) {
$distribution['500-1000']++;
} else {
$distribution['1000+']++;
}
}
foreach ($distribution as &$val) {
$val = $total > 0 ? round($val / $total * 100) : 0;
} }
$this->success('获取成功', null, $distribution); $this->success('获取成功', null, $this->getEmptyStatisticsResult());
} }
/** /**
...@@ -657,70 +644,4 @@ class Dashboard extends Common ...@@ -657,70 +644,4 @@ class Dashboard extends Common
]); ]);
} }
/**
* 格式化时间为相对时间
*
* @param mixed $timestamp 时间戳或日期字符串
* @return string 格式化后的时间字符串
*/
private function formatTime($timestamp)
{
if (empty($timestamp)) {
return '未知时间';
}
// 如果不是数字,尝试转换为时间戳
if (!is_numeric($timestamp)) {
$timestamp = strtotime($timestamp);
if ($timestamp === false) {
return '未知时间';
}
}
$timestamp = (int)$timestamp;
// 验证时间戳是否在合理范围内(1970年到2100年)
if ($timestamp < 0 || $timestamp > 4102444800) {
return '未知时间';
}
$now = time();
$diff = $now - $timestamp;
if ($diff < 60) {
return '刚刚';
} elseif ($diff < 3600) {
return floor($diff / 60) . '分钟前';
} elseif ($diff < 86400) {
return floor($diff / 3600) . '小时前';
} else {
return date('m-d H:i', $timestamp);
}
}
/**
* 从埋点表获取访客数
*
* 使用与定时任务相同的逻辑统计去重访客数
* 用户识别优先级:user_id > session_id > ip
*
* @param int $startTime 开始时间戳
* @param int $endTime 结束时间戳
* @return int 去重后的访客数量
*/
private function getVisitorCountFromTrack($startTime, $endTime)
{
$table = config('database.prefix') . 'shopro_miniprogram_track';
$sql = "SELECT COUNT(DISTINCT CASE
WHEN user_id > 0 THEN CONCAT('u_', user_id)
WHEN session_id <> '' THEN CONCAT('s_', session_id)
ELSE CONCAT('i_', ip)
END) AS total
FROM `{$table}`
WHERE create_time BETWEEN ? AND ?";
$result = Db::query($sql, [$startTime, $endTime]);
return isset($result[0]['total']) ? (int)$result[0]['total'] : 0;
}
} }
\ No newline at end of file
...@@ -180,45 +180,54 @@ class DataStatsCenter extends Command ...@@ -180,45 +180,54 @@ class DataStatsCenter extends Command
$currentRange = $this->buildRange($days, $snapshotTime); $currentRange = $this->buildRange($days, $snapshotTime);
$previousRange = $this->buildRange($days, $currentRange['start'] - 1); $previousRange = $this->buildRange($days, $currentRange['start'] - 1);
// 获取当前周期和上一周期的核心交易指标 $this->customLog('info', '周期时间:[ ' . date('Y-m-d', $currentRange['start']) . '] 至 [ ' . date('Y-m-d', $currentRange['end']) . ']');
$this->customLog('info', '开始获取当前&上一期统计周期核心交易指标');
// 获取当前周期和上一周期的核心交易指标 (成交总额、净利润率,客单价,订单数)
$this->customLog('info', PHP_EOL . '核心指标-开始构建 : 成交总额、净利润率,客单价,订单数,访客数,累计注册用户数,转化率');
$currentRevenue = $this->getRevenueMetrics($currentRange['start'], $currentRange['end']); $currentRevenue = $this->getRevenueMetrics($currentRange['start'], $currentRange['end']);
$this->customLog('info', '核心指标-当前周期-核心交易指标 成交总额、净利润率,客单价,订单数 : ' . json_encode($currentRevenue));
$previousRevenue = $this->getRevenueMetrics($previousRange['start'], $previousRange['end']); $previousRevenue = $this->getRevenueMetrics($previousRange['start'], $previousRange['end']);
$this->customLog('info', '核心指标-上一周期-核心交易指标 成交总额、净利润率,客单价,订单数 : ' . json_encode($previousRevenue));
// 获取当前周期和上一周期的访客数 // 获取当前周期和上一周期的访客数
$this->customLog('info', '开始获取当前&上一期统计周期访客数');
$currentVisitors = $this->getVisitorCount($currentRange['start'], $currentRange['end']); $currentVisitors = $this->getVisitorCount($currentRange['start'], $currentRange['end']);
$this->customLog('info', '核心指标-当前周期-访客数 : ' . $currentVisitors);
$previousVisitors = $this->getVisitorCount($previousRange['start'], $previousRange['end']); $previousVisitors = $this->getVisitorCount($previousRange['start'], $previousRange['end']);
$this->customLog('info', '核心指标-上一周期-访客数 : ' . $previousVisitors);
// 获取累计注册用户数(截至当前周期结束和上一周期结束) // 获取累计注册用户数(截至当前周期结束和上一周期结束)
$this->customLog('info', '开始获取当前&上一期统计周期累计注册用户数');
$currentRegisterTotal = $this->getRegisterCount(0, $currentRange['end']); $currentRegisterTotal = $this->getRegisterCount(0, $currentRange['end']);
$this->customLog('info', '核心指标-当前周期-累计注册用户数 : ' . $currentRegisterTotal);
$previousRegisterTotal = $this->getRegisterCount(0, $previousRange['end']); $previousRegisterTotal = $this->getRegisterCount(0, $previousRange['end']);
$this->customLog('info', '核心指标-上一周期-累计注册用户数 : ' . $previousRegisterTotal);
// 计算转化率(购买用户数/访客数) // 计算转化率(购买用户数/访客数)
$this->customLog('info', '开始计算当前&上一期统计周期转化率');
$currentConversion = $currentVisitors > 0 ? round($currentRevenue['buyer_count'] / $currentVisitors * 100, 2) : 0; $currentConversion = $currentVisitors > 0 ? round($currentRevenue['buyer_count'] / $currentVisitors * 100, 2) : 0;
$previousConversion = $previousVisitors > 0 ? round($previousRevenue['buyer_count'] / $previousVisitors * 100, 2) : 0; $previousConversion = $previousVisitors > 0 ? round($previousRevenue['buyer_count'] / $previousVisitors * 100, 2) : 0;
$this->customLog('info', '核心指标-当前周期-转化率 : ' . $currentConversion . ' 核心指标-上一周期-转化率 : ' . $previousConversion . PHP_EOL);
// 构建趋势数据 // 构建趋势数据
$this->customLog('info', '开始构建当前统计周期趋势数据'); $this->customLog('info', '趋势数据-开始构建 : 成交总额趋势、订单数趋势、注册用户增长趋势');
$trendData = [ $trendData = [
'amount' => $this->buildAmountTrend($days, $snapshotTime), 'amount' => $this->buildAmountTrend($days, $snapshotTime),
'orders' => $this->buildOrderTrend($days, $snapshotTime), 'orders' => $this->buildOrderTrend($days, $snapshotTime),
'register' => $this->buildRegisterTrend($days, $snapshotTime), 'register' => $this->buildRegisterTrend($days, $snapshotTime),
]; ];
$this->customLog('info', '趋势数据-统计结果 : ' . json_encode($trendData) . PHP_EOL);
// 构建漏斗数据 // 构建漏斗数据
$this->customLog('info', '开始构建当前统计周期转化漏斗数据'); $this->customLog('info', '转化漏斗数据-开始构建');
$funnelData = $this->buildFunnelData($currentRange['start'], $currentRange['end']); $funnelData = $this->buildFunnelData($currentRange['start'], $currentRange['end']);
$this->customLog('info', '转化漏斗数据-统计结果 : ' . json_encode($funnelData) . PHP_EOL);
// 构建价格分布 // 构建价格分布
$this->customLog('info', '开始构建当前统计周期客单价分布'); $this->customLog('info', '客单价分布-开始构建');
$priceDistribution = $this->buildPriceDistribution($currentRange['start'], $currentRange['end']); $priceDistribution = $this->buildPriceDistribution($currentRange['start'], $currentRange['end']);
$this->customLog('info', '客单价分布-统计结果 : ' . json_encode($priceDistribution) . PHP_EOL);
// 获取最近订单 // 获取最近订单 页面单独分页访问,不需要在快照中包含,暂时去掉
$this->customLog('info', '开始获取当前统计周期最近订单'); // $this->customLog('info', '开始获取当前统计周期最近订单');
$recentOrders = $this->getRecentOrders(10); // $recentOrders = $this->getRecentOrders(10);
// 组装完整的统计数据结构 // 组装完整的统计数据结构
return [ return [
...@@ -244,7 +253,7 @@ class DataStatsCenter extends Command ...@@ -244,7 +253,7 @@ class DataStatsCenter extends Command
'trend' => $trendData, 'trend' => $trendData,
'funnel' => $funnelData, 'funnel' => $funnelData,
'price_distribution' => $priceDistribution, 'price_distribution' => $priceDistribution,
'recent_orders' => $recentOrders, // 'recent_orders' => $recentOrders,
'extra' => [ 'extra' => [
'current_revenue' => $currentRevenue, 'current_revenue' => $currentRevenue,
'previous_revenue' => $previousRevenue, 'previous_revenue' => $previousRevenue,
...@@ -271,7 +280,7 @@ class DataStatsCenter extends Command ...@@ -271,7 +280,7 @@ class DataStatsCenter extends Command
'trend_json' => json_encode($stats['trend'], JSON_UNESCAPED_UNICODE), 'trend_json' => json_encode($stats['trend'], JSON_UNESCAPED_UNICODE),
'funnel_json' => json_encode($stats['funnel'], JSON_UNESCAPED_UNICODE), 'funnel_json' => json_encode($stats['funnel'], JSON_UNESCAPED_UNICODE),
'distribution_json' => json_encode($stats['price_distribution'], JSON_UNESCAPED_UNICODE), 'distribution_json' => json_encode($stats['price_distribution'], JSON_UNESCAPED_UNICODE),
'recent_orders_json' => json_encode($stats['recent_orders'], JSON_UNESCAPED_UNICODE), // 'recent_orders_json' => json_encode($stats['recent_orders'], JSON_UNESCAPED_UNICODE),
'extra_json' => json_encode($stats['extra'], JSON_UNESCAPED_UNICODE), 'extra_json' => json_encode($stats['extra'], JSON_UNESCAPED_UNICODE),
'updated_at' => $now, 'updated_at' => $now,
]; ];
...@@ -301,7 +310,7 @@ class DataStatsCenter extends Command ...@@ -301,7 +310,7 @@ class DataStatsCenter extends Command
*/ */
protected function buildRange($days, $endTime) protected function buildRange($days, $endTime)
{ {
$endTime = (int)$endTime; $endTime = strtotime(date('Y-m-d 23:59:59', $endTime));
// 计算开始时间:从结束时间往前推 days-1 天的 00:00:00 // 计算开始时间:从结束时间往前推 days-1 天的 00:00:00
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime('-' . ($days - 1) . ' days', $endTime))); $startTime = strtotime(date('Y-m-d 00:00:00', strtotime('-' . ($days - 1) . ' days', $endTime)));
...@@ -332,15 +341,15 @@ class DataStatsCenter extends Command ...@@ -332,15 +341,15 @@ class DataStatsCenter extends Command
->where('paid_time', 'between', [$startTime, $endTime]) ->where('paid_time', 'between', [$startTime, $endTime])
->count(); ->count();
// 统计购买用户数(去重) // 统计购买用户数(去重)-用于计算转化率
$buyerCount = (int)Db::name('shopro_order') $buyerCount = (int)Db::name('shopro_order')
->whereIn('status', ['paid', 'completed']) ->whereIn('status', ['paid', 'completed'])
->where('paid_time', 'between', [$startTime, $endTime]) ->where('paid_time', 'between', [$startTime, $endTime])
->where('user_id', '>', 0) ->where('user_id', '>', 0)
->count('DISTINCT user_id'); ->count('DISTINCT user_id');
// 计算客单价(成交额/订单数) // 计算客单价(成交额/购买用户数)
$avgAmount = $orderCount > 0 ? round($totalAmount / $orderCount, 2) : 0; $avgAmount = $buyerCount > 0 ? round($totalAmount / $buyerCount, 2) : 0;
// 统计商品成本(成本价 × 数量) // 统计商品成本(成本价 × 数量)
$table = $this->getFullTableName('shopro_order_item'); $table = $this->getFullTableName('shopro_order_item');
...@@ -396,6 +405,7 @@ class DataStatsCenter extends Command ...@@ -396,6 +405,7 @@ class DataStatsCenter extends Command
WHERE create_time BETWEEN ? AND ?"; WHERE create_time BETWEEN ? AND ?";
$result = Db::query($sql, [$startTime, $endTime]); $result = Db::query($sql, [$startTime, $endTime]);
$count = isset($result[0]['total']) ? (int)$result[0]['total'] : 0; $count = isset($result[0]['total']) ? (int)$result[0]['total'] : 0;
return $count; return $count;
...@@ -440,7 +450,7 @@ class DataStatsCenter extends Command ...@@ -440,7 +450,7 @@ class DataStatsCenter extends Command
{ {
try { try {
// 获取各阶段的用户数 // 获取各阶段的用户数
$this->customLog('info', '开始获取当前统计周期访问用户数'); $this->customLog('info', '转化漏斗数据-开始获取当前统计周期访问用户数');
$visit = $this->getTrackVisitorCount($startTime, $endTime); $visit = $this->getTrackVisitorCount($startTime, $endTime);
$browse = $this->getTrackGoodViewUserCount($startTime, $endTime); $browse = $this->getTrackGoodViewUserCount($startTime, $endTime);
$cart = $this->getTrackCartAddUserCount($startTime, $endTime); $cart = $this->getTrackCartAddUserCount($startTime, $endTime);
...@@ -448,7 +458,7 @@ class DataStatsCenter extends Command ...@@ -448,7 +458,7 @@ class DataStatsCenter extends Command
$pay = $this->getTrackPayConfirmUserCount($startTime, $endTime); $pay = $this->getTrackPayConfirmUserCount($startTime, $endTime);
// 获取各阶段的事件数 // 获取各阶段的事件数
$this->customLog('info', '开始获取当前统计周期访问事件数'); $this->customLog('info', '转化漏斗数据-开始获取当前统计周期访问事件数');
$visitCount = $this->getTrackVisitCount($startTime, $endTime); $visitCount = $this->getTrackVisitCount($startTime, $endTime);
$browseCount = $this->getTrackGoodViewCount($startTime, $endTime); $browseCount = $this->getTrackGoodViewCount($startTime, $endTime);
$cartCount = $this->getTrackCartAddCount($startTime, $endTime); $cartCount = $this->getTrackCartAddCount($startTime, $endTime);
...@@ -468,6 +478,7 @@ class DataStatsCenter extends Command ...@@ -468,6 +478,7 @@ class DataStatsCenter extends Command
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
$this->customLog('warning', '使用埋点表构建转化漏斗失败' . $e->getMessage()); $this->customLog('warning', '使用埋点表构建转化漏斗失败' . $e->getMessage());
return [];
} }
} }
...@@ -529,7 +540,7 @@ class DataStatsCenter extends Command ...@@ -529,7 +540,7 @@ class DataStatsCenter extends Command
END) as total END) as total
FROM `{$table}` FROM `{$table}`
WHERE create_time BETWEEN ? AND ? WHERE create_time BETWEEN ? AND ?
AND (event_code = 'good_view' OR page_path LIKE '/pages/goods/%')"; AND (event_code = 'good_view' )";
$result = Db::query($sql, [$startTime, $endTime]); $result = Db::query($sql, [$startTime, $endTime]);
return (int)($result[0]['total'] ?? 0); return (int)($result[0]['total'] ?? 0);
} }
......
...@@ -9,10 +9,13 @@ use think\Db; ...@@ -9,10 +9,13 @@ use think\Db;
use think\Queue; use think\Queue;
/** /**
* 每日统计任务 PV UV 等指标 * 每日统计任务 PV UV 等指标
* 每天凌晨1点半执行 * 每天凌晨1点半执行
* *
* 任务描述:每天凌晨1点半执行,统计昨日的PV UV 等指标 * 任务描述:每天凌晨1点半执行,统计昨日的PV UV 等指标
*
* 暂时没有业务用得到这张表,所以先不执行,待后续业务需要时,再开启执行 2026.05.25
*
*/ */
class StatsDaily extends Command class StatsDaily extends Command
{ {
......
...@@ -9,7 +9,7 @@ use think\Db; ...@@ -9,7 +9,7 @@ use think\Db;
use think\Queue; use think\Queue;
/** /**
* 重试失败的埋点数据 PV UV 等指标 * 重试失败的埋点数据 PV UV 等指标
* 每1个小时执行一次 * 每1个小时执行一次
* *
* 任务描述:每1个小时执行一次,重试失败的埋点数据 PV UV 等指标 * 任务描述:每1个小时执行一次,重试失败的埋点数据 PV UV 等指标
...@@ -32,7 +32,7 @@ class TrackFailedRetry extends Command ...@@ -32,7 +32,7 @@ class TrackFailedRetry extends Command
protected function customLog($level, $message) { protected function customLog($level, $message) {
$this->output->writeln($message); $this->output->writeln($message);
custom_log($message, 'stat', $level); custom_log($message, 'stat_track_failed_retry', $level);
} }
protected function execute(Input $input, Output $output) { protected function execute(Input $input, Output $output) {
......
...@@ -494,14 +494,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function ( ...@@ -494,14 +494,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return false; return false;
}); });
} }
// 加载转化漏斗数据
if (data.funnel_data) {
updateFunnel(data.funnel_data);
}
// 加载价格分布数据
if (data.price_distribution_data) {
updatePriceDistribution(data.price_distribution_data);
}
} }
return false; return false;
}); });
// 同时加载其他数据
loadFunnel(timeRange);
loadPriceDistribution(timeRange);
loadRecentOrders();
} }
/** /**
...@@ -557,93 +562,73 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function ( ...@@ -557,93 +562,73 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
} }
/** /**
* 加载转化漏斗数据 * 更新转化漏斗数据
* @param {string} timeRange - 时间范围 * @param {object} funnelData - 漏斗数据对象
*/ */
function loadFunnel(timeRange) { function updateFunnel(funnelData) {
Fast.api.ajax({ var max = funnelData.visit || 1;
url: 'shopro/dashboard/funnel',
type: 'GET',
data: { time_range: timeRange }
}, function (ret, res) {
if (res.code === 1) {
var data = res.data;
var max = data.visit || 1;
var funnelVisit = document.getElementById('funnel-visit'); var funnelVisit = document.getElementById('funnel-visit');
if (funnelVisit) funnelVisit.textContent = formatNumber(data.visit); if (funnelVisit) funnelVisit.textContent = formatNumber(funnelData.visit);
var funnelBrowse = document.getElementById('funnel-browse'); var funnelBrowse = document.getElementById('funnel-browse');
var browsePercent = max > 0 ? Math.round(data.browse / max * 100) : 0; var browsePercent = max > 0 ? Math.round(funnelData.browse / max * 100) : 0;
if (funnelBrowse) { if (funnelBrowse) {
funnelBrowse.textContent = formatNumber(data.browse); funnelBrowse.textContent = formatNumber(funnelData.browse);
funnelBrowse.style.width = browsePercent + '%'; funnelBrowse.style.width = browsePercent + '%';
} }
var funnelCart = document.getElementById('funnel-cart'); var funnelCart = document.getElementById('funnel-cart');
var cartPercent = max > 0 ? Math.round(data.cart / max * 100) : 0; var cartPercent = max > 0 ? Math.round(funnelData.cart / max * 100) : 0;
if (funnelCart) { if (funnelCart) {
funnelCart.textContent = formatNumber(data.cart); funnelCart.textContent = formatNumber(funnelData.cart);
funnelCart.style.width = cartPercent + '%'; funnelCart.style.width = cartPercent + '%';
} }
var funnelSubmit = document.getElementById('funnel-submit'); var funnelSubmit = document.getElementById('funnel-submit');
var submitPercent = max > 0 ? Math.round(data.submit_order / max * 100) : 0; var submitPercent = max > 0 ? Math.round(funnelData.submit_order / max * 100) : 0;
if (funnelSubmit) { if (funnelSubmit) {
funnelSubmit.textContent = formatNumber(data.submit_order); funnelSubmit.textContent = formatNumber(funnelData.submit_order);
funnelSubmit.style.width = submitPercent + '%'; funnelSubmit.style.width = submitPercent + '%';
} }
var funnelPay = document.getElementById('funnel-pay'); var funnelPay = document.getElementById('funnel-pay');
var payPercent = max > 0 ? Math.round(data.pay / max * 100) : 0; var payPercent = max > 0 ? Math.round(funnelData.pay / max * 100) : 0;
if (funnelPay) { if (funnelPay) {
funnelPay.textContent = formatNumber(data.pay); funnelPay.textContent = formatNumber(funnelData.pay);
funnelPay.style.width = payPercent + '%'; funnelPay.style.width = payPercent + '%';
} }
}
return false;
});
} }
/** /**
* 加载价格分布数据 * 更新价格分布数据
* @param {string} timeRange - 时间范围 * @param {object} distributionData - 价格分布数据对象
*/ */
function loadPriceDistribution(timeRange) { function updatePriceDistribution(distributionData) {
Fast.api.ajax({ var price0100 = document.getElementById('price-0-100');
url: 'shopro/dashboard/priceDistribution', var price0100Percent = document.getElementById('price-0-100-percent');
type: 'GET', if (price0100) price0100.style.width = (distributionData['0-100'] || 0) + '%';
data: { time_range: timeRange } if (price0100Percent) price0100Percent.textContent = (distributionData['0-100'] || 0) + '%';
}, function (ret, res) {
if (res.code === 1) { var price100300 = document.getElementById('price-100-300');
var data = res.data; var price100300Percent = document.getElementById('price-100-300-percent');
var price0100 = document.getElementById('price-0-100'); if (price100300) price100300.style.width = (distributionData['100-300'] || 0) + '%';
var price0100Percent = document.getElementById('price-0-100-percent'); if (price100300Percent) price100300Percent.textContent = (distributionData['100-300'] || 0) + '%';
if (price0100) price0100.style.width = data['0-100'] + '%';
if (price0100Percent) price0100Percent.textContent = data['0-100'] + '%'; var price300500 = document.getElementById('price-300-500');
var price300500Percent = document.getElementById('price-300-500-percent');
var price100300 = document.getElementById('price-100-300'); if (price300500) price300500.style.width = (distributionData['300-500'] || 0) + '%';
var price100300Percent = document.getElementById('price-100-300-percent'); if (price300500Percent) price300500Percent.textContent = (distributionData['300-500'] || 0) + '%';
if (price100300) price100300.style.width = data['100-300'] + '%';
if (price100300Percent) price100300Percent.textContent = data['100-300'] + '%'; var price5001000 = document.getElementById('price-500-1000');
var price5001000Percent = document.getElementById('price-500-1000-percent');
var price300500 = document.getElementById('price-300-500'); if (price5001000) price5001000.style.width = (distributionData['500-1000'] || 0) + '%';
var price300500Percent = document.getElementById('price-300-500-percent'); if (price5001000Percent) price5001000Percent.textContent = (distributionData['500-1000'] || 0) + '%';
if (price300500) price300500.style.width = data['300-500'] + '%';
if (price300500Percent) price300500Percent.textContent = data['300-500'] + '%'; var price1000 = document.getElementById('price-1000');
var price1000Percent = document.getElementById('price-1000-percent');
var price5001000 = document.getElementById('price-500-1000'); if (price1000) price1000.style.width = (distributionData['1000+'] || 0) + '%';
var price5001000Percent = document.getElementById('price-500-1000-percent'); if (price1000Percent) price1000Percent.textContent = (distributionData['1000+'] || 0) + '%';
if (price5001000) price5001000.style.width = data['500-1000'] + '%';
if (price5001000Percent) price5001000Percent.textContent = data['500-1000'] + '%';
var price1000 = document.getElementById('price-1000');
var price1000Percent = document.getElementById('price-1000-percent');
if (price1000) price1000.style.width = data['1000+'] + '%';
if (price1000Percent) price1000Percent.textContent = data['1000+'] + '%';
}
return false;
});
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment