Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
loveisland
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
刘小敏
loveisland
Commits
0c953e5c
Commit
0c953e5c
authored
May 25, 2026
by
刘小敏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
数据中心优化;计划任务日志优化;
parent
fb3982b7
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
435 additions
and
516 deletions
+435
-516
Dashboard.php
application/admin/controller/shopro/Dashboard.php
+329
-409
DataStatsCenter.php
application/command/DataStatsCenter.php
+33
-22
StatsDaily.php
application/command/StatsDaily.php
+4
-1
TrackFailedRetry.php
application/command/TrackFailedRetry.php
+2
-2
dashboard.js
public/assets/js/backend/shopro/dashboard.js
+67
-82
No files found.
application/admin/controller/shopro/Dashboard.php
View file @
0c953e5c
...
...
@@ -37,100 +37,77 @@ class Dashboard extends Common
return
$this
->
view
->
fetch
();
}
/**
* 获取核心统计数据
*
* 该接口优先从缓存读取 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
()
private
function
buildStatisticsResult
(
$statsData
,
$period
,
$fromCache
,
$updateTime
)
{
$timeRange
=
$this
->
request
->
param
(
'time_range'
,
'30days'
);
$period
=
$this
->
request
->
param
(
'period'
,
'day'
);
$cacheKey
=
'shopro:data_stats_center:'
.
$timeRange
;
$cachedData
=
cache
(
$cacheKey
);
$summary
=
$statsData
[
'summary'
];
$trendData
=
$this
->
buildTrendDataForChart
(
$statsData
[
'trend'
],
$period
);
$funnelData
=
$this
->
formatFunnelData
(
$statsData
[
'funnel'
]
??
[]);
$priceDistributionData
=
$this
->
formatPriceDistributionData
(
$statsData
[
'price_distribution'
]
??
[]);
// 获取最新统计记录的更新时间
$updateTime
=
Db
::
name
(
'shopro_data_stats_center'
)
->
order
(
'created_at'
,
'desc'
)
->
value
(
'created_at'
);
return
[
'total_amount'
=>
$summary
[
'total_amount'
][
'value'
]
??
0
,
'total_amount_compare_rate'
=>
$summary
[
'total_amount'
][
'compare_rate'
]
??
null
,
'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'
]))
{
$summary
=
$cachedData
[
'summary'
];
$trendData
=
$this
->
buildTrendDataForChart
(
$cachedData
[
'trend'
],
$period
);
// 较上周期 效果计算
// - 上一周期和当前周期数据都是0时 → 显示"持平"
// - 上一周期是0但当前周期有数据 → 显示"+100%"
// - 上一周期有数据但当前周期是0 → 显示"-100%"
// - 两个周期都有数据时 → 显示实际百分比
$result
=
[
'total_amount'
=>
$summary
[
'total_amount'
][
'value'
],
'total_amount_compare_rate'
=>
$summary
[
'total_amount'
][
'compare_rate'
],
'profit_rate'
=>
$summary
[
'profit_rate'
][
'formatted'
],
'profit_rate_compare_rate'
=>
$summary
[
'profit_rate'
][
'compare_rate'
],
'avg_amount'
=>
$summary
[
'avg_amount'
][
'value'
],
'avg_amount_compare_rate'
=>
$summary
[
'avg_amount'
][
'compare_rate'
],
'order_count'
=>
$summary
[
'order_count'
][
'value'
],
'order_count_compare_rate'
=>
$summary
[
'order_count'
][
'compare_rate'
],
'visitor_count'
=>
$summary
[
'visitor_count'
][
'value'
],
'visitor_count_compare_rate'
=>
$summary
[
'visitor_count'
][
'compare_rate'
],
'conversion_rate'
=>
$summary
[
'conversion_rate'
][
'formatted'
],
'conversion_rate_compare_rate'
=>
$summary
[
'conversion_rate'
][
'compare_rate'
],
'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'
)
];
private
function
getEmptyStatisticsResult
()
{
return
[
'total_amount'
=>
0
,
'total_amount_compare_rate'
=>
null
,
'profit_rate'
=>
'0.00%'
,
'profit_rate_compare_rate'
=>
null
,
'avg_amount'
=>
0
,
'avg_amount_compare_rate'
=>
null
,
'order_count'
=>
0
,
'order_count_compare_rate'
=>
null
,
'visitor_count'
=>
0
,
'visitor_count_compare_rate'
=>
null
,
'conversion_rate'
=>
'0.00%'
,
'conversion_rate_compare_rate'
=>
null
,
'register_users'
=>
0
,
'register_users_compare_rate'
=>
null
,
'trend_data'
=>
[],
'funnel_data'
=>
[],
'price_distribution_data'
=>
[],
'from_cache'
=>
false
,
'update_time'
=>
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);
// $trendData = $this->getTrendData($days, $period);
// $result = [
// 'total_amount' => $trendData['total_amount'],
// 'total_amount_compare_rate' => $trendData['total_amount_compare_rate'] ?? 0,
// 'profit_rate' => $trendData['profit_rate'],
// '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);
private
function
formatPriceDistributionData
(
$distribution
)
{
$result
=
[];
foreach
(
$distribution
as
$item
)
{
$result
[
$item
[
'label'
]]
=
$item
[
'rate'
];
}
return
$result
;
}
/**
...
...
@@ -298,137 +275,18 @@ class Dashboard extends Common
* @param string $timeRange 时间范围标识
* @return int 对应的天数
*/
private
function
getDaysByTimeRange
(
$timeRange
)
{
switch
(
$timeRange
)
{
case
'7days'
:
return
7
;
case
'90days'
:
return
90
;
case
'30days'
:
default
:
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
];
}
// private function getDaysByTimeRange($timeRange)
// {
// switch ($timeRange) {
// case '7days':
// return 7;
// case '90days':
// return 90;
// case '30days':
// default:
// return 30;
// }
// }
/**
* 获取日期格式化字符串
...
...
@@ -436,18 +294,18 @@ class Dashboard extends Common
* @param string $period 周期类型
* @return string PHP 日期格式化字符串
*/
private
function
getDateFormat
(
$period
)
{
switch
(
$period
)
{
case
'week'
:
return
'Y年W周'
;
case
'month'
:
return
'Y年m月'
;
case
'day'
:
default
:
return
'm-d'
;
}
}
//
private function getDateFormat($period)
//
{
//
switch ($period) {
//
case 'week':
//
return 'Y年W周';
//
case 'month':
//
return 'Y年m月';
//
case 'day':
//
default:
//
return 'm-d';
//
}
//
}
/**
* 获取转化漏斗数据
...
...
@@ -465,147 +323,276 @@ class Dashboard extends Common
* - submit_order: 提交订单人数(下单的去重用户数)
* - pay: 完成支付人数(已支付或已完成订单的去重用户数)
*/
public
function
funnel
()
{
$timeRange
=
$this
->
request
->
param
(
'time_range'
,
'30days'
);
// public function funnel()
// {
// $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'
];
}
$this
->
success
(
'获取成功'
,
null
,
$result
);
}
/**
* 获取客单价分布
*
* 统计不同价格区间的订单占比
* 优先从缓存读取,缓存未命中时降级到数据库查询
*
* 请求参数:
* - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days'
*
* 返回数据:
* - '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"
);
$visitCount
=
0
;
try
{
$visitCount
=
Db
::
name
(
'shopro_user_goods_log'
)
->
whereBetween
(
'createtime'
,
[
$startTime
,
$endTime
])
->
distinct
(
true
)
->
count
(
'user_id'
);
}
catch
(
\Exception
$e
)
{
$visitCount
=
0
;
/**
* 格式化时间为相对时间
*
* @param mixed $timestamp 时间戳或日期字符串
* @return string 格式化后的时间字符串
*/
private
function
formatTime
(
$timestamp
)
{
if
(
empty
(
$timestamp
))
{
return
'未知时间'
;
}
$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
;
// 如果不是数字,尝试转换为时间戳
if
(
!
is_numeric
(
$timestamp
))
{
$timestamp
=
strtotime
(
$timestamp
);
if
(
$timestamp
===
false
)
{
return
'未知时间'
;
}
}
$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
;
$timestamp
=
(
int
)
$timestamp
;
// 验证时间戳是否在合理范围内(1970年到2100年)
if
(
$timestamp
<
0
||
$timestamp
>
4102444800
)
{
return
'未知时间'
;
}
$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'
);
$now
=
time
();
$diff
=
$now
-
$timestamp
;
$this
->
success
(
'获取成功'
,
null
,
[
'visit'
=>
$visitCount
,
'browse'
=>
$browseCount
,
'cart'
=>
$cartCount
,
'submit_order'
=>
$submitOrderCount
,
'pay'
=>
$payCount
]);
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;
// }
/**
* 获取核心统计数据
*
*
统计不同价格区间的订单占比
*
优先从缓存读取,缓存未命中时降级到数据库查询
*
该接口优先从缓存读取 DataStatsCenter 定时任务生成的统计数据,
*
缓存未命中时降级到数据库实时查询。
*
* 请求参数:
* - time_range: 时间范围,可选值:'7days'、'30days'、'90days',默认 '30days'
* - period: 周期类型,可选值:'day'、'week'、'month',默认 'day'
*
* 返回数据:
* - '0-100': 0-100元订单占比(百分比)
* - '100-300': 100-300元订单占比(百分比)
* - '300-500': 300-500元订单占比(百分比)
* - '500-1000': 500-1000元订单占比(百分比)
* - '1000+': 1000元以上订单占比(百分比)
* - 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
priceDistribution
()
public
function
statistics
()
{
$timeRange
=
$this
->
request
->
param
(
'time_range'
,
'30days'
);
$period
=
$this
->
request
->
param
(
'period'
,
'day'
);
$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'
];
}
if
(
$cachedData
!==
false
&&
isset
(
$cachedData
[
'summary'
])
&&
isset
(
$cachedData
[
'trend'
]))
{
$updateTime
=
Db
::
name
(
'shopro_data_stats_center'
)
->
order
(
'updated_at'
,
'desc'
)
->
value
(
'updated_at'
);
$result
=
$this
->
buildStatisticsResult
(
$cachedData
,
$period
,
true
,
$updateTime
);
$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
;
$latestRecord
=
Db
::
name
(
'shopro_data_stats_center'
)
->
order
(
'created_at'
,
'desc'
)
->
find
();
if
(
$latestRecord
)
{
$statsData
=
[
'summary'
=>
json_decode
(
$latestRecord
[
'summary_json'
],
true
)
?:
[],
'trend'
=>
json_decode
(
$latestRecord
[
'trend_json'
],
true
)
?:
[],
'funnel'
=>
json_decode
(
$latestRecord
[
'funnel_json'
],
true
)
?:
[],
'price_distribution'
=>
json_decode
(
$latestRecord
[
'distribution_json'
],
true
)
?:
[]
];
cache
(
$cacheKey
,
$statsData
,
86400
);
$result
=
$this
->
buildStatisticsResult
(
$statsData
,
$period
,
false
,
$latestRecord
[
'created_at'
]);
$this
->
success
(
'获取成功'
,
null
,
$result
);
}
$this
->
success
(
'获取成功'
,
null
,
$
distribution
);
$this
->
success
(
'获取成功'
,
null
,
$
this
->
getEmptyStatisticsResult
()
);
}
/**
...
...
@@ -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
application/command/DataStatsCenter.php
View file @
0c953e5c
...
...
@@ -180,45 +180,54 @@ class DataStatsCenter extends Command
$currentRange
=
$this
->
buildRange
(
$days
,
$snapshotTime
);
$previousRange
=
$this
->
buildRange
(
$days
,
$currentRange
[
'start'
]
-
1
);
// 获取当前周期和上一周期的核心交易指标
$this
->
customLog
(
'info'
,
'开始获取当前&上一期统计周期核心交易指标'
);
$this
->
customLog
(
'info'
,
'周期时间:[ '
.
date
(
'Y-m-d'
,
$currentRange
[
'start'
])
.
'] 至 [ '
.
date
(
'Y-m-d'
,
$currentRange
[
'end'
])
.
']'
);
// 获取当前周期和上一周期的核心交易指标 (成交总额、净利润率,客单价,订单数)
$this
->
customLog
(
'info'
,
PHP_EOL
.
'核心指标-开始构建 : 成交总额、净利润率,客单价,订单数,访客数,累计注册用户数,转化率'
);
$currentRevenue
=
$this
->
getRevenueMetrics
(
$currentRange
[
'start'
],
$currentRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-当前周期-核心交易指标 成交总额、净利润率,客单价,订单数 : '
.
json_encode
(
$currentRevenue
));
$previousRevenue
=
$this
->
getRevenueMetrics
(
$previousRange
[
'start'
],
$previousRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-上一周期-核心交易指标 成交总额、净利润率,客单价,订单数 : '
.
json_encode
(
$previousRevenue
));
// 获取当前周期和上一周期的访客数
$this
->
customLog
(
'info'
,
'开始获取当前&上一期统计周期访客数'
);
$currentVisitors
=
$this
->
getVisitorCount
(
$currentRange
[
'start'
],
$currentRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-当前周期-访客数 : '
.
$currentVisitors
);
$previousVisitors
=
$this
->
getVisitorCount
(
$previousRange
[
'start'
],
$previousRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-上一周期-访客数 : '
.
$previousVisitors
);
// 获取累计注册用户数(截至当前周期结束和上一周期结束)
$this
->
customLog
(
'info'
,
'开始获取当前&上一期统计周期累计注册用户数'
);
$currentRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$currentRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-当前周期-累计注册用户数 : '
.
$currentRegisterTotal
);
$previousRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$previousRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'核心指标-上一周期-累计注册用户数 : '
.
$previousRegisterTotal
);
// 计算转化率(购买用户数/访客数)
$this
->
customLog
(
'info'
,
'开始计算当前&上一期统计周期转化率'
);
$currentConversion
=
$currentVisitors
>
0
?
round
(
$currentRevenue
[
'buyer_count'
]
/
$currentVisitors
*
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
=
[
'amount'
=>
$this
->
buildAmountTrend
(
$days
,
$snapshotTime
),
'orders'
=>
$this
->
buildOrderTrend
(
$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'
]);
$this
->
customLog
(
'info'
,
'转化漏斗数据-统计结果 : '
.
json_encode
(
$funnelData
)
.
PHP_EOL
);
// 构建价格分布
$this
->
customLog
(
'info'
,
'
开始构建当前统计周期客单价分布
'
);
$this
->
customLog
(
'info'
,
'
客单价分布-开始构建
'
);
$priceDistribution
=
$this
->
buildPriceDistribution
(
$currentRange
[
'start'
],
$currentRange
[
'end'
]);
$this
->
customLog
(
'info'
,
'客单价分布-统计结果 : '
.
json_encode
(
$priceDistribution
)
.
PHP_EOL
);
// 获取最近订单
$this
->
customLog
(
'info'
,
'开始获取当前统计周期最近订单'
);
$recentOrders
=
$this
->
getRecentOrders
(
10
);
// 获取最近订单
页面单独分页访问,不需要在快照中包含,暂时去掉
//
$this->customLog('info', '开始获取当前统计周期最近订单');
//
$recentOrders = $this->getRecentOrders(10);
// 组装完整的统计数据结构
return
[
...
...
@@ -244,7 +253,7 @@ class DataStatsCenter extends Command
'trend'
=>
$trendData
,
'funnel'
=>
$funnelData
,
'price_distribution'
=>
$priceDistribution
,
'recent_orders'
=>
$recentOrders
,
//
'recent_orders' => $recentOrders,
'extra'
=>
[
'current_revenue'
=>
$currentRevenue
,
'previous_revenue'
=>
$previousRevenue
,
...
...
@@ -271,7 +280,7 @@ class DataStatsCenter extends Command
'trend_json'
=>
json_encode
(
$stats
[
'trend'
],
JSON_UNESCAPED_UNICODE
),
'funnel_json'
=>
json_encode
(
$stats
[
'funnel'
],
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
),
'updated_at'
=>
$now
,
];
...
...
@@ -301,7 +310,7 @@ class DataStatsCenter extends Command
*/
protected
function
buildRange
(
$days
,
$endTime
)
{
$endTime
=
(
int
)
$endTime
;
$endTime
=
strtotime
(
date
(
'Y-m-d 23:59:59'
,
$endTime
))
;
// 计算开始时间:从结束时间往前推 days-1 天的 00:00:00
$startTime
=
strtotime
(
date
(
'Y-m-d 00:00:00'
,
strtotime
(
'-'
.
(
$days
-
1
)
.
' days'
,
$endTime
)));
...
...
@@ -332,15 +341,15 @@ class DataStatsCenter extends Command
->
where
(
'paid_time'
,
'between'
,
[
$startTime
,
$endTime
])
->
count
();
// 统计购买用户数(去重)
// 统计购买用户数(去重)
-用于计算转化率
$buyerCount
=
(
int
)
Db
::
name
(
'shopro_order'
)
->
whereIn
(
'status'
,
[
'paid'
,
'completed'
])
->
where
(
'paid_time'
,
'between'
,
[
$startTime
,
$endTime
])
->
where
(
'user_id'
,
'>'
,
0
)
->
count
(
'DISTINCT user_id'
);
// 计算客单价(成交额/
订单
数)
$avgAmount
=
$
orderCount
>
0
?
round
(
$totalAmount
/
$ord
erCount
,
2
)
:
0
;
// 计算客单价(成交额/
购买用户
数)
$avgAmount
=
$
buyerCount
>
0
?
round
(
$totalAmount
/
$buy
erCount
,
2
)
:
0
;
// 统计商品成本(成本价 × 数量)
$table
=
$this
->
getFullTableName
(
'shopro_order_item'
);
...
...
@@ -396,6 +405,7 @@ class DataStatsCenter extends Command
WHERE create_time BETWEEN ? AND ?"
;
$result
=
Db
::
query
(
$sql
,
[
$startTime
,
$endTime
]);
$count
=
isset
(
$result
[
0
][
'total'
])
?
(
int
)
$result
[
0
][
'total'
]
:
0
;
return
$count
;
...
...
@@ -440,7 +450,7 @@ class DataStatsCenter extends Command
{
try
{
// 获取各阶段的用户数
$this
->
customLog
(
'info'
,
'开始获取当前统计周期访问用户数'
);
$this
->
customLog
(
'info'
,
'
转化漏斗数据-
开始获取当前统计周期访问用户数'
);
$visit
=
$this
->
getTrackVisitorCount
(
$startTime
,
$endTime
);
$browse
=
$this
->
getTrackGoodViewUserCount
(
$startTime
,
$endTime
);
$cart
=
$this
->
getTrackCartAddUserCount
(
$startTime
,
$endTime
);
...
...
@@ -448,7 +458,7 @@ class DataStatsCenter extends Command
$pay
=
$this
->
getTrackPayConfirmUserCount
(
$startTime
,
$endTime
);
// 获取各阶段的事件数
$this
->
customLog
(
'info'
,
'开始获取当前统计周期访问事件数'
);
$this
->
customLog
(
'info'
,
'
转化漏斗数据-
开始获取当前统计周期访问事件数'
);
$visitCount
=
$this
->
getTrackVisitCount
(
$startTime
,
$endTime
);
$browseCount
=
$this
->
getTrackGoodViewCount
(
$startTime
,
$endTime
);
$cartCount
=
$this
->
getTrackCartAddCount
(
$startTime
,
$endTime
);
...
...
@@ -468,6 +478,7 @@ class DataStatsCenter extends Command
];
}
catch
(
\Exception
$e
)
{
$this
->
customLog
(
'warning'
,
'使用埋点表构建转化漏斗失败'
.
$e
->
getMessage
());
return
[];
}
}
...
...
@@ -529,7 +540,7 @@ class DataStatsCenter extends Command
END) as total
FROM `
{
$table
}
`
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
]);
return
(
int
)(
$result
[
0
][
'total'
]
??
0
);
}
...
...
application/command/StatsDaily.php
View file @
0c953e5c
...
...
@@ -9,10 +9,13 @@ use think\Db;
use
think\Queue
;
/**
* 每日统计任务 PV UV 等指标
* 每日统计任务 PV UV 等指标
* 每天凌晨1点半执行
*
* 任务描述:每天凌晨1点半执行,统计昨日的PV UV 等指标
*
* 暂时没有业务用得到这张表,所以先不执行,待后续业务需要时,再开启执行 2026.05.25
*
*/
class
StatsDaily
extends
Command
{
...
...
application/command/TrackFailedRetry.php
View file @
0c953e5c
...
...
@@ -9,7 +9,7 @@ use think\Db;
use
think\Queue
;
/**
* 重试失败的埋点数据 PV UV 等指标
* 重试失败的埋点数据 PV UV 等指标
* 每1个小时执行一次
*
* 任务描述:每1个小时执行一次,重试失败的埋点数据 PV UV 等指标
...
...
@@ -32,7 +32,7 @@ class TrackFailedRetry extends Command
protected
function
customLog
(
$level
,
$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
)
{
...
...
public/assets/js/backend/shopro/dashboard.js
View file @
0c953e5c
...
...
@@ -494,14 +494,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return
false
;
});
}
// 加载转化漏斗数据
if
(
data
.
funnel_data
)
{
updateFunnel
(
data
.
funnel_data
);
}
// 加载价格分布数据
if
(
data
.
price_distribution_data
)
{
updatePriceDistribution
(
data
.
price_distribution_data
);
}
}
return
false
;
});
// 同时加载其他数据
loadFunnel
(
timeRange
);
loadPriceDistribution
(
timeRange
);
loadRecentOrders
();
}
/**
...
...
@@ -557,93 +562,73 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
}
/**
*
加载
转化漏斗数据
* @param {
string} timeRange - 时间范围
*
更新
转化漏斗数据
* @param {
object} funnelData - 漏斗数据对象
*/
function
loadFunnel
(
timeRange
)
{
Fast
.
api
.
ajax
({
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
;
function
updateFunnel
(
funnelData
)
{
var
max
=
funnelData
.
visit
||
1
;
var
funnelVisit
=
document
.
getElementById
(
'funnel-visit'
);
if
(
funnelVisit
)
funnelVisit
.
textContent
=
formatNumber
(
d
ata
.
visit
);
var
funnelVisit
=
document
.
getElementById
(
'funnel-visit'
);
if
(
funnelVisit
)
funnelVisit
.
textContent
=
formatNumber
(
funnelD
ata
.
visit
);
var
funnelBrowse
=
document
.
getElementById
(
'funnel-browse'
);
var
browsePercent
=
max
>
0
?
Math
.
round
(
d
ata
.
browse
/
max
*
100
)
:
0
;
if
(
funnelBrowse
)
{
funnelBrowse
.
textContent
=
formatNumber
(
d
ata
.
browse
);
funnelBrowse
.
style
.
width
=
browsePercent
+
'%'
;
}
var
funnelBrowse
=
document
.
getElementById
(
'funnel-browse'
);
var
browsePercent
=
max
>
0
?
Math
.
round
(
funnelD
ata
.
browse
/
max
*
100
)
:
0
;
if
(
funnelBrowse
)
{
funnelBrowse
.
textContent
=
formatNumber
(
funnelD
ata
.
browse
);
funnelBrowse
.
style
.
width
=
browsePercent
+
'%'
;
}
var
funnelCart
=
document
.
getElementById
(
'funnel-cart'
);
var
cartPercent
=
max
>
0
?
Math
.
round
(
d
ata
.
cart
/
max
*
100
)
:
0
;
if
(
funnelCart
)
{
funnelCart
.
textContent
=
formatNumber
(
d
ata
.
cart
);
funnelCart
.
style
.
width
=
cartPercent
+
'%'
;
}
var
funnelCart
=
document
.
getElementById
(
'funnel-cart'
);
var
cartPercent
=
max
>
0
?
Math
.
round
(
funnelD
ata
.
cart
/
max
*
100
)
:
0
;
if
(
funnelCart
)
{
funnelCart
.
textContent
=
formatNumber
(
funnelD
ata
.
cart
);
funnelCart
.
style
.
width
=
cartPercent
+
'%'
;
}
var
funnelSubmit
=
document
.
getElementById
(
'funnel-submit'
);
var
submitPercent
=
max
>
0
?
Math
.
round
(
d
ata
.
submit_order
/
max
*
100
)
:
0
;
if
(
funnelSubmit
)
{
funnelSubmit
.
textContent
=
formatNumber
(
d
ata
.
submit_order
);
funnelSubmit
.
style
.
width
=
submitPercent
+
'%'
;
}
var
funnelSubmit
=
document
.
getElementById
(
'funnel-submit'
);
var
submitPercent
=
max
>
0
?
Math
.
round
(
funnelD
ata
.
submit_order
/
max
*
100
)
:
0
;
if
(
funnelSubmit
)
{
funnelSubmit
.
textContent
=
formatNumber
(
funnelD
ata
.
submit_order
);
funnelSubmit
.
style
.
width
=
submitPercent
+
'%'
;
}
var
funnelPay
=
document
.
getElementById
(
'funnel-pay'
);
var
payPercent
=
max
>
0
?
Math
.
round
(
data
.
pay
/
max
*
100
)
:
0
;
if
(
funnelPay
)
{
funnelPay
.
textContent
=
formatNumber
(
data
.
pay
);
funnelPay
.
style
.
width
=
payPercent
+
'%'
;
}
}
return
false
;
});
var
funnelPay
=
document
.
getElementById
(
'funnel-pay'
);
var
payPercent
=
max
>
0
?
Math
.
round
(
funnelData
.
pay
/
max
*
100
)
:
0
;
if
(
funnelPay
)
{
funnelPay
.
textContent
=
formatNumber
(
funnelData
.
pay
);
funnelPay
.
style
.
width
=
payPercent
+
'%'
;
}
}
/**
*
加载
价格分布数据
* @param {
string} timeRange - 时间范围
*
更新
价格分布数据
* @param {
object} distributionData - 价格分布数据对象
*/
function
loadPriceDistribution
(
timeRange
)
{
Fast
.
api
.
ajax
({
url
:
'shopro/dashboard/priceDistribution'
,
type
:
'GET'
,
data
:
{
time_range
:
timeRange
}
},
function
(
ret
,
res
)
{
if
(
res
.
code
===
1
)
{
var
data
=
res
.
data
;
var
price0100
=
document
.
getElementById
(
'price-0-100'
);
var
price0100Percent
=
document
.
getElementById
(
'price-0-100-percent'
);
if
(
price0100
)
price0100
.
style
.
width
=
data
[
'0-100'
]
+
'%'
;
if
(
price0100Percent
)
price0100Percent
.
textContent
=
data
[
'0-100'
]
+
'%'
;
var
price100300
=
document
.
getElementById
(
'price-100-300'
);
var
price100300Percent
=
document
.
getElementById
(
'price-100-300-percent'
);
if
(
price100300
)
price100300
.
style
.
width
=
data
[
'100-300'
]
+
'%'
;
if
(
price100300Percent
)
price100300Percent
.
textContent
=
data
[
'100-300'
]
+
'%'
;
var
price300500
=
document
.
getElementById
(
'price-300-500'
);
var
price300500Percent
=
document
.
getElementById
(
'price-300-500-percent'
);
if
(
price300500
)
price300500
.
style
.
width
=
data
[
'300-500'
]
+
'%'
;
if
(
price300500Percent
)
price300500Percent
.
textContent
=
data
[
'300-500'
]
+
'%'
;
var
price5001000
=
document
.
getElementById
(
'price-500-1000'
);
var
price5001000Percent
=
document
.
getElementById
(
'price-500-1000-percent'
);
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
;
});
function
updatePriceDistribution
(
distributionData
)
{
var
price0100
=
document
.
getElementById
(
'price-0-100'
);
var
price0100Percent
=
document
.
getElementById
(
'price-0-100-percent'
);
if
(
price0100
)
price0100
.
style
.
width
=
(
distributionData
[
'0-100'
]
||
0
)
+
'%'
;
if
(
price0100Percent
)
price0100Percent
.
textContent
=
(
distributionData
[
'0-100'
]
||
0
)
+
'%'
;
var
price100300
=
document
.
getElementById
(
'price-100-300'
);
var
price100300Percent
=
document
.
getElementById
(
'price-100-300-percent'
);
if
(
price100300
)
price100300
.
style
.
width
=
(
distributionData
[
'100-300'
]
||
0
)
+
'%'
;
if
(
price100300Percent
)
price100300Percent
.
textContent
=
(
distributionData
[
'100-300'
]
||
0
)
+
'%'
;
var
price300500
=
document
.
getElementById
(
'price-300-500'
);
var
price300500Percent
=
document
.
getElementById
(
'price-300-500-percent'
);
if
(
price300500
)
price300500
.
style
.
width
=
(
distributionData
[
'300-500'
]
||
0
)
+
'%'
;
if
(
price300500Percent
)
price300500Percent
.
textContent
=
(
distributionData
[
'300-500'
]
||
0
)
+
'%'
;
var
price5001000
=
document
.
getElementById
(
'price-500-1000'
);
var
price5001000Percent
=
document
.
getElementById
(
'price-500-1000-percent'
);
if
(
price5001000
)
price5001000
.
style
.
width
=
(
distributionData
[
'500-1000'
]
||
0
)
+
'%'
;
if
(
price5001000Percent
)
price5001000Percent
.
textContent
=
(
distributionData
[
'500-1000'
]
||
0
)
+
'%'
;
var
price1000
=
document
.
getElementById
(
'price-1000'
);
var
price1000Percent
=
document
.
getElementById
(
'price-1000-percent'
);
if
(
price1000
)
price1000
.
style
.
width
=
(
distributionData
[
'1000+'
]
||
0
)
+
'%'
;
if
(
price1000Percent
)
price1000Percent
.
textContent
=
(
distributionData
[
'1000+'
]
||
0
)
+
'%'
;
}
/**
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment