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
c64d3b9c
Commit
c64d3b9c
authored
May 23, 2026
by
刘小敏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
数据中心优化
parent
32f0f5e6
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
468 additions
and
331 deletions
+468
-331
Dashboard.php
application/admin/controller/shopro/Dashboard.php
+99
-25
index.html
application/admin/view/shopro/dashboard/index.html
+25
-30
DataStatsCenter.php
application/command/DataStatsCenter.php
+11
-3
addons.js
public/assets/js/addons.js
+8
-2
dashboard.js
public/assets/js/backend/shopro/dashboard.js
+325
-271
No files found.
application/admin/controller/shopro/Dashboard.php
View file @
c64d3b9c
...
@@ -36,24 +36,6 @@ class Dashboard extends Common
...
@@ -36,24 +36,6 @@ class Dashboard extends Common
*/
*/
public
function
index
()
public
function
index
()
{
{
// $cardList = [
// [
// 'name' => 'total',
// 'card' => 'shopro/dashboard/total',
// 'status' => $this->auth->check('shopro/dashboard/total')
// ],
// [
// 'name' => 'chart',
// 'card' => 'shopro/dashboard/chart',
// 'status' => $this->auth->check('shopro/dashboard/chart')
// ],
// [
// 'name' => 'ranking',
// 'card' => 'shopro/dashboard/ranking',
// 'status' => $this->auth->check('shopro/dashboard/ranking')
// ],
// ];
// $this->assignconfig('cardList', $cardList);
return
$this
->
view
->
fetch
();
return
$this
->
view
->
fetch
();
}
}
...
@@ -90,10 +72,20 @@ class Dashboard extends Common
...
@@ -90,10 +72,20 @@ class Dashboard extends Common
$cacheKey
=
'shopro:data_stats_center:'
.
$timeRange
;
$cacheKey
=
'shopro:data_stats_center:'
.
$timeRange
;
$cachedData
=
cache
(
$cacheKey
);
$cachedData
=
cache
(
$cacheKey
);
// 获取最新统计记录的更新时间
$updateTime
=
Db
::
name
(
'shopro_data_stats_center'
)
->
order
(
'created_at'
,
'desc'
)
->
value
(
'created_at'
);
if
(
$cachedData
!==
false
&&
isset
(
$cachedData
[
'summary'
])
&&
isset
(
$cachedData
[
'trend'
]))
{
if
(
$cachedData
!==
false
&&
isset
(
$cachedData
[
'summary'
])
&&
isset
(
$cachedData
[
'trend'
]))
{
$summary
=
$cachedData
[
'summary'
];
$summary
=
$cachedData
[
'summary'
];
$trendData
=
$this
->
buildTrendDataForChart
(
$cachedData
[
'trend'
],
$period
);
$trendData
=
$this
->
buildTrendDataForChart
(
$cachedData
[
'trend'
],
$period
);
// 较上周期 效果计算
// - 上一周期和当前周期数据都是0时 → 显示"持平"
// - 上一周期是0但当前周期有数据 → 显示"+100%"
// - 上一周期有数据但当前周期是0 → 显示"-100%"
// - 两个周期都有数据时 → 显示实际百分比
$result
=
[
$result
=
[
'total_amount'
=>
$summary
[
'total_amount'
][
'value'
],
'total_amount'
=>
$summary
[
'total_amount'
][
'value'
],
'total_amount_compare_rate'
=>
$summary
[
'total_amount'
][
'compare_rate'
],
'total_amount_compare_rate'
=>
$summary
[
'total_amount'
][
'compare_rate'
],
...
@@ -110,7 +102,8 @@ class Dashboard extends Common
...
@@ -110,7 +102,8 @@ class Dashboard extends Common
'register_users'
=>
$summary
[
'register_users'
][
'value'
],
'register_users'
=>
$summary
[
'register_users'
][
'value'
],
'register_users_compare_rate'
=>
$summary
[
'register_users'
][
'compare_rate'
],
'register_users_compare_rate'
=>
$summary
[
'register_users'
][
'compare_rate'
],
'trend_data'
=>
$trendData
,
'trend_data'
=>
$trendData
,
'from_cache'
=>
true
'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
);
$this
->
success
(
'获取成功'
,
null
,
$result
);
...
@@ -121,14 +114,22 @@ class Dashboard extends Common
...
@@ -121,14 +114,22 @@ class Dashboard extends Common
$result
=
[
$result
=
[
'total_amount'
=>
$trendData
[
'total_amount'
],
'total_amount'
=>
$trendData
[
'total_amount'
],
'total_amount_compare_rate'
=>
$trendData
[
'total_amount_compare_rate'
]
??
0
,
'profit_rate'
=>
$trendData
[
'profit_rate'
],
'profit_rate'
=>
$trendData
[
'profit_rate'
],
'profit_rate_compare_rate'
=>
$trendData
[
'profit_rate_compare_rate'
]
??
0
,
'avg_amount'
=>
$trendData
[
'avg_amount'
],
'avg_amount'
=>
$trendData
[
'avg_amount'
],
'avg_amount_compare_rate'
=>
$trendData
[
'avg_amount_compare_rate'
]
??
0
,
'order_count'
=>
$trendData
[
'order_count'
],
'order_count'
=>
$trendData
[
'order_count'
],
'order_count_compare_rate'
=>
$trendData
[
'order_count_compare_rate'
]
??
0
,
'visitor_count'
=>
$trendData
[
'visitor_count'
],
'visitor_count'
=>
$trendData
[
'visitor_count'
],
'visitor_count_compare_rate'
=>
$trendData
[
'visitor_count_compare_rate'
]
??
0
,
'conversion_rate'
=>
$trendData
[
'conversion_rate'
],
'conversion_rate'
=>
$trendData
[
'conversion_rate'
],
'conversion_rate_compare_rate'
=>
$trendData
[
'conversion_rate_compare_rate'
]
??
0
,
'register_users'
=>
$trendData
[
'register_users'
],
'register_users'
=>
$trendData
[
'register_users'
],
'register_users_compare_rate'
=>
$trendData
[
'register_users_compare_rate'
]
??
0
,
'trend_data'
=>
$trendData
[
'trend_data'
],
'trend_data'
=>
$trendData
[
'trend_data'
],
'from_cache'
=>
false
'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
);
$this
->
success
(
'获取成功'
,
null
,
$result
);
...
@@ -327,6 +328,7 @@ class Dashboard extends Common
...
@@ -327,6 +328,7 @@ class Dashboard extends Common
$orderCount
=
0
;
$orderCount
=
0
;
$registerUsers
=
0
;
$registerUsers
=
0
;
$visitorCount
=
0
;
$visitorCount
=
0
;
$buyerCount
=
0
;
$format
=
$this
->
getDateFormat
(
$period
);
$format
=
$this
->
getDateFormat
(
$period
);
...
@@ -343,13 +345,16 @@ class Dashboard extends Common
...
@@ -343,13 +345,16 @@ class Dashboard extends Common
->
whereIn
(
'status'
,
[
Order
::
STATUS_PAID
,
Order
::
STATUS_COMPLETED
])
->
whereIn
(
'status'
,
[
Order
::
STATUS_PAID
,
Order
::
STATUS_COMPLETED
])
->
count
();
->
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
();
$registerNum
=
User
::
whereBetween
(
'createtime'
,
[
$startTime
,
$endTime
])
->
count
();
try
{
try
{
$visitorNum
=
Db
::
name
(
'shopro_user_goods_log'
)
$visitorNum
=
$this
->
getVisitorCountFromTrack
(
$startTime
,
$endTime
);
->
whereBetween
(
'createtime'
,
[
$startTime
,
$endTime
])
->
distinct
(
true
)
->
count
(
'user_id'
);
}
catch
(
\Exception
$e
)
{
}
catch
(
\Exception
$e
)
{
$visitorNum
=
0
;
$visitorNum
=
0
;
}
}
...
@@ -365,20 +370,63 @@ class Dashboard extends Common
...
@@ -365,20 +370,63 @@ class Dashboard extends Common
$orderCount
+=
$orderNum
;
$orderCount
+=
$orderNum
;
$registerUsers
+=
$registerNum
;
$registerUsers
+=
$registerNum
;
$visitorCount
+=
$visitorNum
;
$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
;
$avgAmount
=
$orderCount
>
0
?
bcdiv
(
$totalAmount
,
$orderCount
,
2
)
:
0
;
$profitRate
=
$totalAmount
>
0
?
'18.6%'
:
'0%'
;
$profitRate
=
$totalAmount
>
0
?
'18.6%'
:
'0%'
;
$conversionRate
=
$visitorCount
>
0
?
bcdiv
(
$orderCount
*
100
,
$visitorCount
,
2
)
.
'%'
:
'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
[
return
[
'total_amount'
=>
$totalAmount
,
'total_amount'
=>
$totalAmount
,
'total_amount_compare_rate'
=>
$totalAmountCompareRate
,
'order_count'
=>
$orderCount
,
'order_count'
=>
$orderCount
,
'order_count_compare_rate'
=>
$orderCountCompareRate
,
'avg_amount'
=>
$avgAmount
,
'avg_amount'
=>
$avgAmount
,
'avg_amount_compare_rate'
=>
$avgAmountCompareRate
,
'profit_rate'
=>
$profitRate
,
'profit_rate'
=>
$profitRate
,
'profit_rate_compare_rate'
=>
0
,
'visitor_count'
=>
$visitorCount
,
'visitor_count'
=>
$visitorCount
,
'visitor_count_compare_rate'
=>
$visitorCountCompareRate
,
'conversion_rate'
=>
$conversionRate
,
'conversion_rate'
=>
$conversionRate
,
'conversion_rate_compare_rate'
=>
$conversionRateCompareRate
,
'register_users'
=>
$registerUsers
,
'register_users'
=>
$registerUsers
,
'register_users_compare_rate'
=>
$registerUsersCompareRate
,
'trend_data'
=>
$data
'trend_data'
=>
$data
];
];
}
}
...
@@ -630,4 +678,29 @@ class Dashboard extends Common
...
@@ -630,4 +678,29 @@ class Dashboard extends Common
}
}
}
}
/**
* 从埋点表获取访客数
*
* 使用与定时任务相同的逻辑统计去重访客数
* 用户识别优先级: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/admin/view/shopro/dashboard/index.html
View file @
c64d3b9c
...
@@ -367,6 +367,7 @@
...
@@ -367,6 +367,7 @@
<el-container
class=
"panel-block"
>
<el-container
class=
"panel-block"
>
<el-main
class=
"dashboard-index-main"
>
<el-main
class=
"dashboard-index-main"
>
<el-scrollbar
height=
"100%"
>
<el-scrollbar
height=
"100%"
>
<div
class=
"stats-center"
>
<div
class=
"stats-center"
>
<div
class=
"stats-center-header"
>
<div
class=
"stats-center-header"
>
<div>
<div>
...
@@ -377,23 +378,25 @@
...
@@ -377,23 +378,25 @@
<div
class=
"stats-center-time"
>
实时业务数据监控 · 更新于
<span
id=
"update-time"
>
{:date('Y-m-d H:i:s')}
</span></div>
<div
class=
"stats-center-time"
>
实时业务数据监控 · 更新于
<span
id=
"update-time"
>
{:date('Y-m-d H:i:s')}
</span></div>
</div>
</div>
<div
class=
"header-right"
>
<div
class=
"header-right"
>
<div
class=
"data-status"
>
<
!-- <
div class="data-status">
<span class="status-dot"></span>
<span class="status-dot"></span>
<span>数据同步中</span>
<span>数据同步中</span>
</div>
</div>
-->
<div
class=
"time-select-wrapper"
>
<div
class=
"time-select-wrapper"
>
<select
id=
"time_range"
class=
"time-select"
onchange=
"loadStatistics(this.value)"
>
<select
id=
"time_range"
class=
"time-select"
>
<option
value=
"7days"
>
近7天
</option>
<option
value=
"7days"
>
近7天
</option>
<option
value=
"30days"
selected
>
近30天
</option>
<option
value=
"30days"
selected
>
近30天
</option>
<option
value=
"90days"
>
近90天
</option>
<option
value=
"90days"
>
近90天
</option>
</select>
</select>
</div>
</div>
<button
class=
"refresh-btn"
id=
"refreshBtn"
onclick=
"refreshAllData()"
>
<button
class=
"refresh-btn"
id=
"refreshBtn"
>
<i
class=
"fa fa-refresh"
></i>
<i
class=
"fa fa-refresh"
></i>
<span>
刷新数据
</span>
<span>
刷新数据
</span>
</button>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"stats-center"
>
<div
class=
"stats-card-row"
>
<div
class=
"stats-card-row"
>
<div
class=
"stats-card card-1"
>
<div
class=
"stats-card card-1"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -401,7 +404,7 @@
...
@@ -401,7 +404,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-money"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-money"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-total-amount"
>
¥0.00
</div>
<div
class=
"stats-card-value"
id=
"stat-total-amount"
>
¥0.00
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-total-trend"
>
+0%
</span>
较上
月
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-total-trend"
>
+0%
</span>
较上
周期
</div>
</div>
</div>
<div
class=
"stats-card card-2"
>
<div
class=
"stats-card card-2"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -409,7 +412,7 @@
...
@@ -409,7 +412,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-percent"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-percent"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-profit-rate"
>
0%
</div>
<div
class=
"stats-card-value"
id=
"stat-profit-rate"
>
0%
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-profit-trend"
>
+0%
</span>
较上
月
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-profit-trend"
>
+0%
</span>
较上
周期
</div>
</div>
</div>
<div
class=
"stats-card card-3"
>
<div
class=
"stats-card card-3"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -417,7 +420,7 @@
...
@@ -417,7 +420,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-shopping-cart"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-shopping-cart"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-avg-amount"
>
¥0.00
</div>
<div
class=
"stats-card-value"
id=
"stat-avg-amount"
>
¥0.00
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-avg-trend"
>
+0%
</span>
较上
月
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-avg-trend"
>
+0%
</span>
较上
周期
</div>
</div>
</div>
<div
class=
"stats-card card-4"
>
<div
class=
"stats-card card-4"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -425,7 +428,7 @@
...
@@ -425,7 +428,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-file-text"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-file-text"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-order-count"
>
0
</div>
<div
class=
"stats-card-value"
id=
"stat-order-count"
>
0
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-order-trend"
>
+0%
</span>
较上
月
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-order-trend"
>
+0%
</span>
较上
周期
</div>
</div>
</div>
<div
class=
"stats-card card-5"
>
<div
class=
"stats-card card-5"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -433,7 +436,7 @@
...
@@ -433,7 +436,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-users"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-users"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-visitor-count"
>
0
</div>
<div
class=
"stats-card-value"
id=
"stat-visitor-count"
>
0
</div>
<div
class=
"stats-card-trend
"
id=
"stat-visitor-trend"
><i
class=
"fa fa-arrow-down"
></i>
<span>
-0%
</span>
较上月
</div>
<div
class=
"stats-card-trend
up"
id=
"stat-visitor-trend"
><i
class=
"fa fa-arrow-up"
></i>
<span>
+0%
</span>
较上周期
</div>
</div>
</div>
<div
class=
"stats-card card-6"
>
<div
class=
"stats-card card-6"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -441,15 +444,7 @@
...
@@ -441,15 +444,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-line-chart"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-line-chart"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-conversion-rate"
>
0%
</div>
<div
class=
"stats-card-value"
id=
"stat-conversion-rate"
>
0%
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-conversion-trend"
>
+0%
</span>
较上月
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-conversion-trend"
>
+0%
</span>
较上周期
</div>
</div>
<div
class=
"stats-card card-7"
>
<div
class=
"stats-card-header"
>
<span
class=
"stats-card-label"
>
新增用户
</span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-user-plus"
></i></span>
</div>
<div
class=
"stats-card-value"
id=
"stat-new-users"
>
0
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-new-trend"
>
+0(+0%)
</span></div>
</div>
</div>
<div
class=
"stats-card card-8"
>
<div
class=
"stats-card card-8"
>
<div
class=
"stats-card-header"
>
<div
class=
"stats-card-header"
>
...
@@ -457,7 +452,7 @@
...
@@ -457,7 +452,7 @@
<span
class=
"stats-card-icon"
><i
class=
"fa fa-users"
></i></span>
<span
class=
"stats-card-icon"
><i
class=
"fa fa-users"
></i></span>
</div>
</div>
<div
class=
"stats-card-value"
id=
"stat-register-users"
>
0
</div>
<div
class=
"stats-card-value"
id=
"stat-register-users"
>
0
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-register-trend"
>
+0
(+0%)
</span>
</div>
<div
class=
"stats-card-trend up"
><i
class=
"fa fa-arrow-up"
></i>
<span
id=
"stat-register-trend"
>
+0
%
</span>
较上周期
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -470,11 +465,11 @@
...
@@ -470,11 +465,11 @@
<div
class=
"chart-panel-title"
>
成交总额趋势
</div>
<div
class=
"chart-panel-title"
>
成交总额趋势
</div>
<div
class=
"chart-panel-subtitle"
id=
"gmv-subtitle"
>
近30天每日GMV走势
</div>
<div
class=
"chart-panel-subtitle"
id=
"gmv-subtitle"
>
近30天每日GMV走势
</div>
</div>
</div>
<div
class=
"chart-tabs"
>
<
!-- <
div class="chart-tabs">
<button
class=
"chart-tab active"
id=
"gmv-day"
onclick=
"switchPeriod('gmv', 'day')
"
>
日
</button>
<button class="chart-tab active" id="gmv-day"
data-chart="gmv" data-period="day
">日</button>
<button
class=
"chart-tab"
id=
"gmv-week"
onclick=
"switchPeriod('gmv', 'week')
"
>
周
</button>
<button class="chart-tab" id="gmv-week"
data-chart="gmv" data-period="week
">周</button>
<button
class=
"chart-tab"
id=
"gmv-month"
onclick=
"switchPeriod('gmv', 'month')
"
>
月
</button>
<button class="chart-tab" id="gmv-month"
data-chart="gmv" data-period="month
">月</button>
</div>
</div>
-->
</div>
</div>
<div
id=
"gmv-chart"
class=
"chart-container"
></div>
<div
id=
"gmv-chart"
class=
"chart-container"
></div>
</div>
</div>
...
@@ -486,11 +481,11 @@
...
@@ -486,11 +481,11 @@
<div
class=
"chart-panel-title"
>
订单数趋势
</div>
<div
class=
"chart-panel-title"
>
订单数趋势
</div>
<div
class=
"chart-panel-subtitle"
id=
"order-subtitle"
>
近30天订单量走势
</div>
<div
class=
"chart-panel-subtitle"
id=
"order-subtitle"
>
近30天订单量走势
</div>
</div>
</div>
<div
class=
"chart-tabs"
>
<
!-- <
div class="chart-tabs">
<button
class=
"chart-tab active"
id=
"order-day"
onclick=
"switchPeriod('order', 'day')
"
>
日
</button>
<button class="chart-tab active" id="order-day"
data-chart="order" data-period="day
">日</button>
<button
class=
"chart-tab"
id=
"order-week"
onclick=
"switchPeriod('order', 'week')
"
>
周
</button>
<button class="chart-tab" id="order-week"
data-chart="order" data-period="week
">周</button>
<button
class=
"chart-tab"
id=
"order-month"
onclick=
"switchPeriod('order', 'month')
"
>
月
</button>
<button class="chart-tab" id="order-month"
data-chart="order" data-period="month
">月</button>
</div>
</div>
-->
</div>
</div>
<div
id=
"order-chart"
class=
"chart-container"
></div>
<div
id=
"order-chart"
class=
"chart-container"
></div>
</div>
</div>
...
@@ -548,7 +543,7 @@
...
@@ -548,7 +543,7 @@
</div>
</div>
<div>
<div>
<span
class=
"growth-badge"
id=
"growth-badge"
>
+0
</span>
<span
class=
"growth-badge"
id=
"growth-badge"
>
+0
</span>
<p
class=
"
growth-badge-text
"
>
累计注册用户
</p>
<p
class=
"
chart-panel-subtitle
"
>
累计注册用户
</p>
</div>
</div>
</div>
</div>
<div
id=
"user-growth-chart"
class=
"chart-container"
></div>
<div
id=
"user-growth-chart"
class=
"chart-container"
></div>
...
...
application/command/DataStatsCenter.php
View file @
c64d3b9c
...
@@ -195,10 +195,10 @@ class DataStatsCenter extends Command
...
@@ -195,10 +195,10 @@ class DataStatsCenter extends Command
$currentRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$currentRange
[
'end'
]);
$currentRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$currentRange
[
'end'
]);
$previousRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$previousRange
[
'end'
]);
$previousRegisterTotal
=
$this
->
getRegisterCount
(
0
,
$previousRange
[
'end'
]);
// 计算转化率(
订单
数/访客数)
// 计算转化率(
购买用户
数/访客数)
$this
->
customLog
(
'info'
,
'开始计算当前&上一期统计周期转化率'
);
$this
->
customLog
(
'info'
,
'开始计算当前&上一期统计周期转化率'
);
$currentConversion
=
$currentVisitors
>
0
?
round
(
$currentRevenue
[
'
ord
er_count'
]
/
$currentVisitors
*
100
,
2
)
:
0
;
$currentConversion
=
$currentVisitors
>
0
?
round
(
$currentRevenue
[
'
buy
er_count'
]
/
$currentVisitors
*
100
,
2
)
:
0
;
$previousConversion
=
$previousVisitors
>
0
?
round
(
$previousRevenue
[
'
ord
er_count'
]
/
$previousVisitors
*
100
,
2
)
:
0
;
$previousConversion
=
$previousVisitors
>
0
?
round
(
$previousRevenue
[
'
buy
er_count'
]
/
$previousVisitors
*
100
,
2
)
:
0
;
// 构建趋势数据
// 构建趋势数据
$this
->
customLog
(
'info'
,
'开始构建当前统计周期趋势数据'
);
$this
->
customLog
(
'info'
,
'开始构建当前统计周期趋势数据'
);
...
@@ -332,6 +332,13 @@ class DataStatsCenter extends Command
...
@@ -332,6 +332,13 @@ class DataStatsCenter extends Command
->
where
(
'paid_time'
,
'between'
,
[
$startTime
,
$endTime
])
->
where
(
'paid_time'
,
'between'
,
[
$startTime
,
$endTime
])
->
count
();
->
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
/
$orderCount
,
2
)
:
0
;
$avgAmount
=
$orderCount
>
0
?
round
(
$totalAmount
/
$orderCount
,
2
)
:
0
;
...
@@ -355,6 +362,7 @@ class DataStatsCenter extends Command
...
@@ -355,6 +362,7 @@ class DataStatsCenter extends Command
return
[
return
[
'total_amount'
=>
$totalAmount
,
'total_amount'
=>
$totalAmount
,
'order_count'
=>
$orderCount
,
'order_count'
=>
$orderCount
,
'buyer_count'
=>
$buyerCount
,
'avg_amount'
=>
$avgAmount
,
'avg_amount'
=>
$avgAmount
,
'cost_amount'
=>
$costAmount
,
'cost_amount'
=>
$costAmount
,
'refund_amount'
=>
$refundAmount
,
'refund_amount'
=>
$refundAmount
,
...
...
public/assets/js/addons.js
View file @
c64d3b9c
...
@@ -50,6 +50,9 @@ if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.a
...
@@ -50,6 +50,9 @@ if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.a
'ElementPlus'
:
{
'ElementPlus'
:
{
deps
:
[
'css!../addons/shopro/libs/element-plus/index.css'
]
deps
:
[
'css!../addons/shopro/libs/element-plus/index.css'
]
},
},
'ElementPlusIconsVue'
:
{
deps
:
[
'ElementPlusIconsVue3'
]
}
},
},
});
});
require
([
'vue3'
,
'ElementPlusIconsVue3'
],
function
(
Vue3
,
ElementPlusIconsVue3
)
{
require
([
'vue3'
,
'ElementPlusIconsVue3'
],
function
(
Vue3
,
ElementPlusIconsVue3
)
{
...
@@ -75,8 +78,11 @@ if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.a
...
@@ -75,8 +78,11 @@ if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.a
const
app
=
createApp
({})
const
app
=
createApp
({})
app
.
use
(
ElementPlus
)
app
.
use
(
ElementPlus
)
for
(
const
[
key
,
component
]
of
Object
.
entries
(
ElementPlusIconsVue
))
{
app
.
component
(
key
,
component
)
if
(
ElementPlusIconsVue
&&
typeof
ElementPlusIconsVue
===
'object'
)
{
for
(
const
[
key
,
component
]
of
Object
.
entries
(
ElementPlusIconsVue
))
{
app
.
component
(
key
,
component
)
}
}
}
app
.
component
(
'sa-chat'
,
SaChat
)
app
.
component
(
'sa-chat'
,
SaChat
)
...
...
public/assets/js/backend/shopro/dashboard.js
View file @
c64d3b9c
...
@@ -4,54 +4,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -4,54 +4,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
index
:
()
=>
{
index
:
()
=>
{
const
{
reactive
,
onMounted
}
=
Vue
;
const
{
reactive
,
onMounted
}
=
Vue
;
// ECharts图表实例对象
// gmvChart: 成交总额趋势图表
// orderChart: 订单数趋势图表
// userGrowthChart: 用户增长图表(活跃用户/新增用户)
var
gmvChart
,
orderChart
,
userGrowthChart
;
var
gmvChart
,
orderChart
,
userGrowthChart
;
// 当前时间范围筛选条件,默认近30天
var
currentTimeRange
=
'30days'
;
var
currentTimeRange
=
'30days'
;
// 成交总额图表的周期(day/week/month)
var
currentGmvPeriod
=
'day'
;
var
currentGmvPeriod
=
'day'
;
// 订单数图表的周期(day/week/month)
var
currentOrderPeriod
=
'day'
;
var
currentOrderPeriod
=
'day'
;
const
index
=
{
const
index
=
{
setup
()
{
setup
()
{
const
state
=
reactive
({
// 图表配置响应式对象
total
:
{
// 用于底部统计图表的Tab切换(订单数/支付订单/支付金额)
user
:
{
data
:
{},
color
:
'#806AF6'
,
color1
:
'rgba(128, 106, 246, 0.4)'
,
color2
:
'rgba(128, 106, 246, 0)'
,
title
:
'用户数量'
,
tip
:
'今日新增用户'
,
footer
:
'本周新增用户人数'
,
},
agent
:
{
data
:
{},
color
:
'#409EFF'
,
color1
:
'rgba(64, 158, 255, 0.4)'
,
color2
:
'rgba(64, 158, 255, 0)'
,
title
:
'分销商数量'
,
tip
:
'今日新增人数'
,
footer
:
'本周新增分销商人数'
,
},
share
:
{
data
:
{},
color
:
'#21C732'
,
color1
:
'rgba(33, 199, 50, 0.4)'
,
color2
:
'rgba(33, 199, 50, 0)'
,
title
:
'分享次数'
,
tip
:
'今日分享次数'
,
footer
:
'本周新增分享次数'
,
},
},
});
const
chart
=
reactive
({
const
chart
=
reactive
({
tabsData
:
{
tabsData
:
{
order
:
'订单数'
,
order
:
'订单数'
,
payOrder
:
'支付订单'
,
payOrder
:
'支付订单'
,
payAmount
:
'支付金额'
,
payAmount
:
'支付金额'
,
},
},
tabActive
:
'order'
,
tabActive
:
'order'
,
// 当前激活的Tab
dateTime
:
getTimeSlot
(),
dateTime
:
getTimeSlot
(),
// 时间选择器的时间范围
shortcuts
:
[
shortcuts
:
[
// 时间快捷选项
{
{
text
:
'今天'
,
text
:
'今天'
,
value
:
getTimeSlot
(),
value
:
getTimeSlot
(),
...
@@ -70,31 +48,34 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -70,31 +48,34 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
},
},
],
],
data
:
{
data
:
{
payAmountArr
:
[],
payAmountArr
:
[],
// 支付金额数据数组
payOrderArr
:
[],
payOrderArr
:
[],
// 支付订单数据数组
orderArr
:
[],
orderArr
:
[],
// 订单数数据数组
},
},
});
});
// 统计数据响应式对象
// 存储仪表盘顶部统计卡片的数据
const
statistics
=
reactive
({
const
statistics
=
reactive
({
total_amount
:
0
,
total_amount
:
0
,
// 成交总额
total_amount_compare_rate
:
0
,
total_amount_compare_rate
:
0
,
// 成交总额环比增长率
profit_rate
:
'0%'
,
profit_rate
:
'0%'
,
// 净利润率
profit_rate_compare_rate
:
0
,
profit_rate_compare_rate
:
0
,
// 净利润率环比增长率
avg_amount
:
0
,
avg_amount
:
0
,
// 客单价
avg_amount_compare_rate
:
0
,
avg_amount_compare_rate
:
0
,
// 客单价环比增长率
order_count
:
0
,
order_count
:
0
,
// 订单数
order_count_compare_rate
:
0
,
order_count_compare_rate
:
0
,
// 订单数环比增长率
visitor_count
:
0
,
visitor_count
:
0
,
// 访客数
visitor_count_compare_rate
:
0
,
visitor_count_compare_rate
:
0
,
// 访客数环比增长率
conversion_rate
:
'0%'
,
conversion_rate
:
'0%'
,
// 转化率
conversion_rate_compare_rate
:
0
,
conversion_rate_compare_rate
:
0
,
// 转化率环比增长率
register_users
:
0
,
register_users
:
0
,
// 注册用户数
register_users_compare_rate
:
0
,
register_users_compare_rate
:
0
,
// 注册用户环比增长率
trend_data
:
[],
trend_data
:
[],
// 趋势数据数组
from_cache
:
false
from_cache
:
false
// 数据是否来自缓存
});
});
// 底部统计图表的配置选项
const
chartOption
=
reactive
({
const
chartOption
=
reactive
({
grid
:
{
grid
:
{
left
:
'10px'
,
left
:
'10px'
,
...
@@ -161,6 +142,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -161,6 +142,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
],
],
});
});
/**
* 生成日期标签数组
* @param {number} days - 需要生成的天数
* @returns {Array} - 日期标签数组,格式如 ['1月1日', '1月2日', ...]
*/
function
generateDateLabels
(
days
)
{
function
generateDateLabels
(
days
)
{
var
labels
=
[];
var
labels
=
[];
for
(
var
i
=
days
-
1
;
i
>=
0
;
i
--
)
{
for
(
var
i
=
days
-
1
;
i
>=
0
;
i
--
)
{
...
@@ -171,6 +157,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -171,6 +157,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return
labels
;
return
labels
;
}
}
/**
* 格式化数字显示
* @param {number} num - 需要格式化的数字
* @param {number} decimals - 小数位数(可选)
* @returns {string} - 格式化后的数字字符串
*/
function
formatNumber
(
num
,
decimals
)
{
function
formatNumber
(
num
,
decimals
)
{
if
(
num
===
null
||
num
===
undefined
)
num
=
0
;
if
(
num
===
null
||
num
===
undefined
)
num
=
0
;
if
(
decimals
!==
undefined
)
{
if
(
decimals
!==
undefined
)
{
...
@@ -179,39 +171,60 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -179,39 +171,60 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return
parseInt
(
num
).
toLocaleString
();
return
parseInt
(
num
).
toLocaleString
();
}
}
/**
* 更新趋势元素显示
* @param {HTMLElement} element - 趋势元素DOM节点
* @param {number|null} compareRate - 环比增长率,null表示持平
*/
function
updateTrendElement
(
element
,
compareRate
)
{
function
updateTrendElement
(
element
,
compareRate
)
{
if
(
!
element
)
return
;
if
(
!
element
)
return
;
var
spanElement
=
element
.
querySelector
(
'span'
);
if
(
!
spanElement
)
return
;
if
(
compareRate
===
null
||
compareRate
===
undefined
)
{
spanElement
.
textContent
=
'持平'
;
element
.
classList
.
remove
(
'up'
,
'down'
);
var
icon
=
element
.
querySelector
(
'i'
);
if
(
icon
)
{
icon
.
classList
.
remove
(
'fa-arrow-up'
,
'fa-arrow-down'
);
}
return
;
}
compareRate
=
parseFloat
(
compareRate
)
||
0
;
compareRate
=
parseFloat
(
compareRate
)
||
0
;
var
trendText
=
''
;
var
trendText
=
''
;
var
trendClass
=
''
;
var
trendClass
=
''
;
if
(
compareRate
>
0
)
{
var
roundedRate
=
Math
.
round
(
compareRate
);
trendText
=
'+'
+
compareRate
.
toFixed
(
2
)
+
'%'
;
if
(
roundedRate
>
0
)
{
trendText
=
'+'
+
roundedRate
+
'%'
;
trendClass
=
'up'
;
trendClass
=
'up'
;
}
else
if
(
compare
Rate
<
0
)
{
}
else
if
(
rounded
Rate
<
0
)
{
trendText
=
compareRate
.
toFixed
(
2
)
+
'%'
;
trendText
=
roundedRate
+
'%'
;
trendClass
=
'down'
;
trendClass
=
'down'
;
}
else
{
}
else
{
trendText
=
'+0%'
;
trendText
=
'+0%'
;
trendClass
=
'up'
;
trendClass
=
'up'
;
}
}
element
.
textContent
=
trendText
;
spanElement
.
textContent
=
trendText
;
var
parentDiv
=
element
.
parentElement
;
element
.
classList
.
remove
(
'up'
,
'down'
);
if
(
parentDiv
)
{
element
.
classList
.
add
(
trendClass
);
parentDiv
.
classList
.
remove
(
'up'
,
'down'
);
parentDiv
.
classList
.
add
(
trendClass
);
var
icon
=
element
.
querySelector
(
'i'
);
if
(
icon
)
{
var
icon
=
parentDiv
.
querySelector
(
'i'
);
icon
.
classList
.
remove
(
'fa-arrow-up'
,
'fa-arrow-down'
);
if
(
icon
)
{
icon
.
classList
.
add
(
trendClass
===
'up'
?
'fa-arrow-up'
:
'fa-arrow-down'
);
icon
.
classList
.
remove
(
'fa-arrow-up'
,
'fa-arrow-down'
);
icon
.
classList
.
add
(
trendClass
===
'up'
?
'fa-arrow-up'
:
'fa-arrow-down'
);
}
}
}
}
}
/**
* 初始化ECharts图表实例
* 创建成交总额趋势、订单数趋势和用户增长图表
*/
function
initCharts
()
{
function
initCharts
()
{
gmvChart
=
echarts
.
init
(
document
.
getElementById
(
'gmv-chart'
));
gmvChart
=
echarts
.
init
(
document
.
getElementById
(
'gmv-chart'
));
var
gmvOption
=
{
var
gmvOption
=
{
...
@@ -336,6 +349,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -336,6 +349,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
};
};
userGrowthChart
.
setOption
(
userGrowthOption
);
userGrowthChart
.
setOption
(
userGrowthOption
);
// 监听窗口大小变化,自动调整图表尺寸
window
.
addEventListener
(
'resize'
,
function
()
{
window
.
addEventListener
(
'resize'
,
function
()
{
gmvChart
.
resize
();
gmvChart
.
resize
();
orderChart
.
resize
();
orderChart
.
resize
();
...
@@ -343,6 +357,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -343,6 +357,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
});
});
}
}
/**
* 切换图表周期(日/周/月)
* @param {string} chartType - 图表类型:'gmv' 或 'order'
* @param {string} period - 周期类型:'day'、'week' 或 'month'
*/
function
switchPeriod
(
chartType
,
period
)
{
function
switchPeriod
(
chartType
,
period
)
{
if
(
chartType
===
'gmv'
)
{
if
(
chartType
===
'gmv'
)
{
currentGmvPeriod
=
period
;
currentGmvPeriod
=
period
;
...
@@ -381,6 +400,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -381,6 +400,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
loadStatistics
(
currentTimeRange
);
loadStatistics
(
currentTimeRange
);
}
}
/**
* 加载统计数据
* @param {string} timeRange - 时间范围,如 '7days'、'30days'、'90days'
*/
function
loadStatistics
(
timeRange
)
{
function
loadStatistics
(
timeRange
)
{
currentTimeRange
=
timeRange
;
currentTimeRange
=
timeRange
;
Fast
.
api
.
ajax
({
Fast
.
api
.
ajax
({
...
@@ -410,6 +433,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -410,6 +433,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
statistics
.
trend_data
=
data
.
trend_data
;
statistics
.
trend_data
=
data
.
trend_data
;
statistics
.
from_cache
=
data
.
from_cache
;
statistics
.
from_cache
=
data
.
from_cache
;
// 更新页面显示的更新时间
var
updateTimeElement
=
document
.
getElementById
(
'update-time'
);
if
(
updateTimeElement
&&
data
.
update_time
)
{
updateTimeElement
.
textContent
=
data
.
update_time
;
console
.
log
(
data
.
update_time
);
}
// 更新统计卡片显示
var
statTotalAmount
=
document
.
getElementById
(
'stat-total-amount'
);
var
statTotalAmount
=
document
.
getElementById
(
'stat-total-amount'
);
var
statProfitRate
=
document
.
getElementById
(
'stat-profit-rate'
);
var
statProfitRate
=
document
.
getElementById
(
'stat-profit-rate'
);
var
statAvgAmount
=
document
.
getElementById
(
'stat-avg-amount'
);
var
statAvgAmount
=
document
.
getElementById
(
'stat-avg-amount'
);
...
@@ -442,9 +473,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -442,9 +473,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
updateGmvChart
(
data
.
trend_data
);
updateGmvChart
(
data
.
trend_data
);
// 如果GMV和订单图表周期相同,复用同一数据
if
(
currentGmvPeriod
===
currentOrderPeriod
)
{
if
(
currentGmvPeriod
===
currentOrderPeriod
)
{
updateOrderChart
(
data
.
trend_data
);
updateOrderChart
(
data
.
trend_data
);
}
else
{
}
else
{
// 周期不同时单独请求订单数据
Fast
.
api
.
ajax
({
Fast
.
api
.
ajax
({
url
:
'shopro/dashboard/statistics'
,
url
:
'shopro/dashboard/statistics'
,
type
:
'GET'
,
type
:
'GET'
,
...
@@ -463,11 +496,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -463,11 +496,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return
false
;
return
false
;
});
});
// 同时加载其他数据
loadFunnel
(
timeRange
);
loadFunnel
(
timeRange
);
loadPriceDistribution
(
timeRange
);
loadPriceDistribution
(
timeRange
);
loadRecentOrders
();
loadRecentOrders
();
}
}
/**
* 更新成交总额趋势图表和用户增长图表
* @param {Array} trendData - 趋势数据数组
*/
function
updateGmvChart
(
trendData
)
{
function
updateGmvChart
(
trendData
)
{
var
labels
=
trendData
.
map
(
function
(
item
)
{
return
item
.
date
;
});
var
labels
=
trendData
.
map
(
function
(
item
)
{
return
item
.
date
;
});
var
amounts
=
trendData
.
map
(
function
(
item
)
{
return
item
.
amount
;
});
var
amounts
=
trendData
.
map
(
function
(
item
)
{
return
item
.
amount
;
});
...
@@ -479,6 +517,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -479,6 +517,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
xAxis
:
{
data
:
labels
},
xAxis
:
{
data
:
labels
},
series
:
[{
data
:
amounts
}]
series
:
[{
data
:
amounts
}]
});
});
gmvChart
.
resize
();
}
}
if
(
userGrowthChart
)
{
if
(
userGrowthChart
)
{
...
@@ -489,13 +528,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -489,13 +528,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
{
data
:
registers
}
{
data
:
registers
}
]
]
});
});
userGrowthChart
.
resize
();
}
}
// 更新用户增长徽章
var
totalRegister
=
registers
.
reduce
(
function
(
a
,
b
)
{
return
a
+
b
;
},
0
);
var
totalRegister
=
registers
.
reduce
(
function
(
a
,
b
)
{
return
a
+
b
;
},
0
);
var
growthBadge
=
document
.
getElementById
(
'growth-badge'
);
var
growthBadge
=
document
.
getElementById
(
'growth-badge'
);
if
(
growthBadge
)
growthBadge
.
textContent
=
'+'
+
formatNumber
(
totalRegister
);
if
(
growthBadge
)
growthBadge
.
textContent
=
'+'
+
formatNumber
(
totalRegister
);
}
}
/**
* 更新订单数趋势图表
* @param {Array} trendData - 趋势数据数组
*/
function
updateOrderChart
(
trendData
)
{
function
updateOrderChart
(
trendData
)
{
var
labels
=
trendData
.
map
(
function
(
item
)
{
return
item
.
date
;
});
var
labels
=
trendData
.
map
(
function
(
item
)
{
return
item
.
date
;
});
var
orders
=
trendData
.
map
(
function
(
item
)
{
return
item
.
order_num
;
});
var
orders
=
trendData
.
map
(
function
(
item
)
{
return
item
.
order_num
;
});
...
@@ -505,9 +550,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -505,9 +550,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
xAxis
:
{
data
:
labels
},
xAxis
:
{
data
:
labels
},
series
:
[{
data
:
orders
}]
series
:
[{
data
:
orders
}]
});
});
orderChart
.
resize
();
}
}
}
}
/**
* 加载转化漏斗数据
* @param {string} timeRange - 时间范围
*/
function
loadFunnel
(
timeRange
)
{
function
loadFunnel
(
timeRange
)
{
Fast
.
api
.
ajax
({
Fast
.
api
.
ajax
({
url
:
'shopro/dashboard/funnel'
,
url
:
'shopro/dashboard/funnel'
,
...
@@ -553,6 +603,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -553,6 +603,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
});
});
}
}
/**
* 加载价格分布数据
* @param {string} timeRange - 时间范围
*/
function
loadPriceDistribution
(
timeRange
)
{
function
loadPriceDistribution
(
timeRange
)
{
Fast
.
api
.
ajax
({
Fast
.
api
.
ajax
({
url
:
'shopro/dashboard/priceDistribution'
,
url
:
'shopro/dashboard/priceDistribution'
,
...
@@ -590,6 +644,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -590,6 +644,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
});
});
}
}
/**
* 加载最近订单列表
*/
function
loadRecentOrders
()
{
function
loadRecentOrders
()
{
Fast
.
api
.
ajax
({
Fast
.
api
.
ajax
({
url
:
'shopro/dashboard/recentOrders'
,
url
:
'shopro/dashboard/recentOrders'
,
...
@@ -615,6 +672,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -615,6 +672,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
});
});
}
}
/**
* 刷新所有数据
*/
function
refreshAllData
()
{
function
refreshAllData
()
{
var
refreshBtn
=
document
.
getElementById
(
'refreshBtn'
);
var
refreshBtn
=
document
.
getElementById
(
'refreshBtn'
);
var
updateTime
=
document
.
getElementById
(
'update-time'
);
var
updateTime
=
document
.
getElementById
(
'update-time'
);
...
@@ -634,134 +694,134 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -634,134 +694,134 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
}
}
function
getTotal
()
{
function
getTotal
()
{
Fast
.
api
.
ajax
({
//
Fast.api.ajax({
url
:
'shopro/dashboard/total'
,
//
url: 'shopro/dashboard/total',
type
:
'GET'
,
//
type: 'GET',
},
function
(
ret
,
res
)
{
//
}, function (ret, res) {
state
.
total
.
agent
.
data
=
res
.
data
.
agent_data
;
//
state.total.agent.data = res.data.agent_data;
state
.
total
.
share
.
data
=
res
.
data
.
share_data
;
//
state.total.share.data = res.data.share_data;
state
.
total
.
user
.
data
=
res
.
data
.
user_data
;
//
state.total.user.data = res.data.user_data;
for
(
var
key
in
state
.
total
)
{
//
for (var key in state.total) {
if
(
state
.
total
[
key
].
data
)
{
//
if (state.total[key].data) {
initChartTotal
(
key
);
//
initChartTotal(key);
}
//
}
}
//
}
return
false
;
//
return false;
},
function
(
ret
,
res
)
{
});
//
}, function (ret, res) { });
}
}
function
initChartTotal
(
key
)
{
function
initChartTotal
(
key
)
{
var
chartEl
=
document
.
getElementById
(
key
+
'Total'
);
//
var chartEl = document.getElementById(key + 'Total');
if
(
!
chartEl
)
return
;
//
if (!chartEl) return;
var
myChart
=
echarts
.
init
(
chartEl
);
//
var myChart = echarts.init(chartEl);
window
.
onresize
=
()
=>
{
//
window.onresize = () => {
myChart
.
resize
();
//
myChart.resize();
};
//
};
var
option
=
{
//
var option = {
grid
:
{
//
grid: {
left
:
6
,
//
left: 6,
top
:
6
,
//
top: 6,
right
:
6
,
//
right: 6,
bottom
:
6
,
//
bottom: 6,
},
//
},
tooltip
:
{
//
tooltip: {
trigger
:
'axis'
,
//
trigger: 'axis',
axisPointer
:
{
//
axisPointer: {
type
:
'none'
,
//
type: 'none',
},
//
},
},
//
},
xAxis
:
{
//
xAxis: {
type
:
'category'
,
//
type: 'category',
data
:
[
'分'
,
20
,
36
,
10
,
10
,
20
],
//
data: ['分', 20, 36, 10, 10, 20],
boundaryGap
:
false
,
//
boundaryGap: false,
axisTick
:
{
//
axisTick: {
show
:
false
,
//
show: false,
},
//
},
axisLabel
:
{
//
axisLabel: {
show
:
false
,
//
show: false,
},
//
},
axisLine
:
{
//
axisLine: {
show
:
false
,
//
show: false,
},
//
},
},
//
},
yAxis
:
{
//
yAxis: {
type
:
'value'
,
//
type: 'value',
splitLine
:
{
//
splitLine: {
show
:
false
,
//
show: false,
},
//
},
axisLabel
:
{
//
axisLabel: {
show
:
false
,
//
show: false,
},
//
},
},
//
},
series
:
[{
//
series: [{
data
:
[
5
,
20
,
36
,
10
,
10
,
20
],
//
data: [5, 20, 36, 10, 10, 20],
type
:
'line'
,
//
type: 'line',
smooth
:
true
,
//
smooth: true,
showSymbol
:
false
,
//
showSymbol: false,
symbol
:
'circle'
,
//
symbol: 'circle',
symbolSize
:
6
,
//
symbolSize: 6,
itemStyle
:
{
//
itemStyle: {
color
:
state
.
total
[
key
].
color
,
//
color: state.total[key].color,
},
//
},
areaStyle
:
{
//
areaStyle: {
color
:
new
echarts
.
graphic
.
LinearGradient
(
//
color: new echarts.graphic.LinearGradient(
0
,
//
0,
0
,
//
0,
0
,
//
0,
1
,
//
1,
[
//
[
{
//
{
offset
:
0
,
//
offset: 0,
color
:
state
.
total
[
key
].
color1
,
//
color: state.total[key].color1,
},
//
},
{
//
{
offset
:
1
,
//
offset: 1,
color
:
state
.
total
[
key
].
color2
,
//
color: state.total[key].color2,
},
//
},
],
//
],
false
,
//
false,
),
//
),
},
//
},
lineStyle
:
{
//
lineStyle: {
width
:
2
,
//
width: 2,
},
//
},
}]
//
}]
};
//
};
initIntervalTotal
(
24
,
'hours'
,
option
,
key
);
//
initIntervalTotal(24, 'hours', option, key);
myChart
.
setOption
(
option
);
//
myChart.setOption(option);
}
}
function
initIntervalTotal
(
interval
,
kld
,
option
,
key
)
{
function
initIntervalTotal
(
interval
,
kld
,
option
,
key
)
{
let
dateTime
=
Moment
().
format
(
'YYYY-MM-DD'
)
+
' 00:00:00'
;
//
let dateTime = Moment().format('YYYY-MM-DD') + ' 00:00:00';
let
x
=
[];
//
let x = [];
let
y
=
[];
//
let y = [];
let
timeStamp
=
[];
//
let timeStamp = [];
for
(
let
i
=
0
;
i
<=
interval
;
i
++
)
{
//
for (let i = 0; i <= interval; i++) {
x
.
push
(
Moment
(
dateTime
).
add
(
i
,
kld
).
format
(
'HH:mm'
));
//
x.push(Moment(dateTime).add(i, kld).format('HH:mm'));
y
.
push
(
0
);
//
y.push(0);
timeStamp
.
push
(
Moment
(
dateTime
).
add
(
i
,
kld
).
valueOf
());
//
timeStamp.push(Moment(dateTime).add(i, kld).valueOf());
}
//
}
x
.
forEach
((
item
,
index
)
=>
{
//
x.forEach((item, index) => {
state
.
total
[
key
].
data
.
array
.
forEach
((
item
)
=>
{
//
state.total[key].data.array.forEach((item) => {
if
(
//
if (
timeStamp
[
index
+
1
]
&&
//
timeStamp[index + 1] &&
item
.
createtime_unix
>
timeStamp
[
index
]
&&
//
item.createtime_unix > timeStamp[index] &&
item
.
createtime_unix
<=
timeStamp
[
index
+
1
]
//
item.createtime_unix <= timeStamp[index + 1]
)
{
//
) {
y
[
index
]
++
;
//
y[index]++;
}
//
}
});
//
});
});
//
});
option
.
xAxis
.
data
=
x
;
//
option.xAxis.data = x;
option
.
series
[
0
].
data
=
y
;
//
option.series[0].data = y;
}
}
function
onChangeTabActive
(
type
)
{
//
function onChangeTabActive(type) {
chart
.
tabActive
=
type
;
//
chart.tabActive = type;
chartOption
.
series
[
0
].
name
=
chart
.
tabsData
[
chart
.
tabActive
];
//
chartOption.series[0].name = chart.tabsData[chart.tabActive];
initChart
();
//
initChart();
}
//
}
function
onChangeDateTime
(
e
)
{
function
onChangeDateTime
(
e
)
{
e
&&
getChart
();
e
&&
getChart
();
...
@@ -887,100 +947,94 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
...
@@ -887,100 +947,94 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
return
timeSlot
;
return
timeSlot
;
}
}
const
ranking
=
reactive
({
// const ranking = reactive({
goods
:
[],
// goods: [],
hot_search
:
[]
// hot_search: []
});
// });
const
pieOption
=
reactive
({
// const pieOption = reactive({
tooltip
:
{
// tooltip: {
trigger
:
'item'
,
// trigger: 'item',
formatter
:
'{a} <br/>{b}: {c} ({d}%)'
,
// formatter: '{a} <br/>{b}: {c} ({d}%)',
},
// },
legend
:
{
// legend: {
show
:
false
,
// show: false,
},
// },
series
:
[
// series: [
{
// {
name
:
'热搜榜'
,
// name: '热搜榜',
type
:
'pie'
,
// type: 'pie',
radius
:
[
'52%'
,
'90%'
],
// radius: ['52%', '90%'],
avoidLabelOverlap
:
false
,
// avoidLabelOverlap: false,
label
:
{
// label: {
show
:
false
,
// show: false,
position
:
'center'
,
// position: 'center',
},
// },
zlevel
:
1
,
// zlevel: 1,
z
:
1
,
// z: 1,
emphasis
:
{
// emphasis: {
label
:
{
// label: {
show
:
true
,
// show: true,
fontSize
:
'16'
,
// fontSize: '16',
fontWeight
:
'normal'
,
// fontWeight: 'normal',
},
// },
},
// },
labelLine
:
{
// labelLine: {
show
:
false
,
// show: false,
},
// },
data
:
[],
// data: [],
},
// },
],
// ],
});
// });
// function getRanking() {
// function onOpen(status) {
// Fast.api.ajax({
// Fast.api.open('shopro/order/order/index?status=' + status + '&createtime=' + encodeURI(JSON.stringify(chart.dateTime)), "订单", {
// url: 'shopro/dashboard/ranking',
// callback() {
// type: 'GET',
// getChart();
// }, function (ret, res) {
// ranking.goods = res.data.goods;
// ranking.hot_search = res.data.hot_search;
// pieOption.series[0].data = [];
// ranking.hot_search.forEach(item => {
// pieOption.series[0].data.push({
// name: item.keyword,
// value: item.num,
// });
// });
// var rankingContentEl = document.getElementById('rankingContent');
// if (rankingContentEl) {
// var myChart3 = echarts.init(rankingContentEl);
// window.onresize = () => {
// myChart3.resize();
// };
// myChart3.setOption(pieOption);
// }
// }
// return false;
// });
// }, function (ret, res) { });
// }
// }
function
onOpen
(
status
)
{
Fast
.
api
.
open
(
'shopro/order/order/index?status='
+
status
+
'&createtime='
+
encodeURI
(
JSON
.
stringify
(
chart
.
dateTime
)),
"订单"
,
{
callback
()
{
getChart
();
}
});
}
onMounted
(()
=>
{
onMounted
(()
=>
{
initCharts
();
initCharts
();
setTimeout
(
function
()
{
if
(
gmvChart
)
gmvChart
.
resize
();
if
(
orderChart
)
orderChart
.
resize
();
if
(
userGrowthChart
)
userGrowthChart
.
resize
();
},
100
);
loadStatistics
(
'30days'
);
loadStatistics
(
'30days'
);
var
timeRangeSelect
=
document
.
getElementById
(
'time_range'
);
if
(
timeRangeSelect
)
{
timeRangeSelect
.
addEventListener
(
'change'
,
function
(
e
)
{
loadStatistics
(
e
.
target
.
value
);
});
}
document
.
querySelectorAll
(
'.chart-tab'
).
forEach
(
btn
=>
{
btn
.
addEventListener
(
'click'
,
function
(
e
)
{
var
chartType
=
this
.
dataset
.
chart
;
var
period
=
this
.
dataset
.
period
;
switchPeriod
(
chartType
,
period
);
});
});
var
refreshBtn
=
document
.
getElementById
(
'refreshBtn'
);
if
(
refreshBtn
)
{
refreshBtn
.
addEventListener
(
'click'
,
refreshAllData
);
}
});
});
return
{
return
{
state
,
getTotal
,
getTotal
,
chart
,
onChangeTabActive
,
onChangeDateTime
,
onChangeDateTime
,
statistics
,
statistics
,
getChart
,
chartOption
,
chartOption
,
initChart
,
initChart
,
drawX
,
drawX
,
getTimeSlot
,
getTimeSlot
,
ranking
,
onOpen
,
initCharts
,
initCharts
,
loadStatistics
,
loadStatistics
,
updateGmvChart
,
updateGmvChart
,
...
...
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