Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
ca35e1a4
authored
Dec 16, 2023
by
owen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
统计:增加商品统计
parent
80355f3a
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
492 additions
and
25 deletions
+492
-25
src/api/mall/statistics/product.ts
+52
-0
src/api/mall/statistics/trade.ts
+2
-2
src/config/axios/service.ts
+4
-20
src/utils/index.ts
+9
-0
src/views/mall/statistics/product/components/ProductRank.vue
+104
-0
src/views/mall/statistics/product/components/ProductSummary.vue
+304
-0
src/views/mall/statistics/product/index.vue
+14
-0
src/views/mall/statistics/trade/index.vue
+3
-3
No files found.
src/api/mall/statistics/product.ts
0 → 100644
View file @
ca35e1a4
import
request
from
'@/config/axios'
import
{
DataComparisonRespVO
}
from
'@/api/mall/statistics/common'
export
interface
ProductStatisticsVO
{
id
:
number
day
:
string
spuId
:
number
spuName
:
string
spuPicUrl
:
string
browseCount
:
number
browseUserCount
:
number
favoriteCount
:
number
cartCount
:
number
orderCount
:
number
orderPayCount
:
number
orderPayPrice
:
number
afterSaleCount
:
number
afterSaleRefundPrice
:
number
browseConvertPercent
:
number
}
// 商品统计 API
export
const
ProductStatisticsApi
=
{
// 获得商品统计分析
getProductStatisticsAnalyse
:
(
params
:
any
)
=>
{
return
request
.
get
<
DataComparisonRespVO
<
ProductStatisticsVO
>>
({
url
:
'/statistics/product/analyse'
,
params
})
},
// 获得商品状况明细
getProductStatisticsList
:
(
params
:
any
)
=>
{
return
request
.
get
<
ProductStatisticsVO
[]
>
({
url
:
'/statistics/product/list'
,
params
})
},
// 导出获得商品状况明细 Excel
exportProductStatisticsExcel
:
(
params
:
any
)
=>
{
return
request
.
download
({
url
:
'/statistics/product/export-excel'
,
params
})
},
// 获得商品排行榜分页
getProductStatisticsRankPage
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/statistics/product/rank-page`
,
params
})
}
}
src/api/mall/statistics/trade.ts
View file @
ca35e1a4
...
@@ -66,9 +66,9 @@ export const getTradeStatisticsSummary = () => {
...
@@ -66,9 +66,9 @@ export const getTradeStatisticsSummary = () => {
}
}
// 获得交易状况统计
// 获得交易状况统计
export
const
getTrade
TrendSummary
=
(
params
:
TradeTrendReqVO
)
=>
{
export
const
getTrade
StatisticsAnalyse
=
(
params
:
TradeTrendReqVO
)
=>
{
return
request
.
get
<
DataComparisonRespVO
<
TradeTrendSummaryRespVO
>>
({
return
request
.
get
<
DataComparisonRespVO
<
TradeTrendSummaryRespVO
>>
({
url
:
'/statistics/trade/
trend/summary
'
,
url
:
'/statistics/trade/
analyse
'
,
params
:
formatDateParam
(
params
)
params
:
formatDateParam
(
params
)
})
})
}
}
...
...
src/config/axios/service.ts
View file @
ca35e1a4
...
@@ -70,27 +70,11 @@ service.interceptors.request.use(
...
@@ -70,27 +70,11 @@ service.interceptors.request.use(
}
}
// get参数编码
// get参数编码
if
(
config
.
method
?.
toUpperCase
()
===
'GET'
&&
params
)
{
if
(
config
.
method
?.
toUpperCase
()
===
'GET'
&&
params
)
{
let
url
=
config
.
url
+
'?'
for
(
const
propName
of
Object
.
keys
(
params
))
{
const
value
=
params
[
propName
]
if
(
value
!==
void
0
&&
value
!==
null
&&
typeof
value
!==
'undefined'
)
{
if
(
typeof
value
===
'object'
)
{
for
(
const
val
of
Object
.
keys
(
value
))
{
const
params
=
propName
+
'['
+
val
+
']'
const
subPart
=
encodeURIComponent
(
params
)
+
'='
url
+=
subPart
+
encodeURIComponent
(
value
[
val
])
+
'&'
}
}
else
{
url
+=
`
${
propName
}
=
${
encodeURIComponent
(
value
)}
&`
}
}
}
// 给 get 请求加上时间戳参数,避免从缓存中拿数据
// const now = new Date().getTime()
// params = params.substring(0, url.length - 1) + `?_t=${now}`
url
=
url
.
slice
(
0
,
-
1
)
config
.
params
=
{}
config
.
params
=
{}
config
.
url
=
url
const
paramsStr
=
qs
.
stringify
(
params
,
{
allowDots
:
true
})
if
(
paramsStr
)
{
config
.
url
=
config
.
url
+
'?'
+
paramsStr
}
}
}
return
config
return
config
},
},
...
...
src/utils/index.ts
View file @
ca35e1a4
...
@@ -285,3 +285,12 @@ export const getUrlValue = (key: string, urlStr: string = location.href): string
...
@@ -285,3 +285,12 @@ export const getUrlValue = (key: string, urlStr: string = location.href): string
export
const
getUrlNumberValue
=
(
key
:
string
,
urlStr
:
string
=
location
.
href
):
number
=>
{
export
const
getUrlNumberValue
=
(
key
:
string
,
urlStr
:
string
=
location
.
href
):
number
=>
{
return
toNumber
(
getUrlValue
(
key
,
urlStr
))
return
toNumber
(
getUrlValue
(
key
,
urlStr
))
}
}
/**
* 构建排序字段
* @param prop 字段名称
* @param order 顺序
*/
export
const
buildSortingField
=
({
prop
,
order
})
=>
{
return
{
field
:
prop
,
order
:
order
===
'ascending'
?
'asc'
:
'desc'
}
}
src/views/mall/statistics/product/components/ProductRank.vue
0 → 100644
View file @
ca35e1a4
<
template
>
<el-card
shadow=
"never"
>
<template
#
header
>
<!-- 标题 -->
<div
class=
"flex flex-row items-center justify-between"
>
<CardTitle
title=
"商品排行"
/>
<!-- 查询条件 -->
<ShortcutDateRangePicker
ref=
"shortcutDateRangePicker"
@
change=
"handleDateRangeChange"
/>
</div>
</
template
>
<!-- 排行列表 -->
<el-table
v-loading=
"loading"
:data=
"list"
@
sort-change=
"handleSortChange"
>
<el-table-column
label=
"商品ID"
prop=
"spuId"
min-width=
"70"
/>
<el-table-column
label=
"商品图片"
align=
"center"
prop=
"picUrl"
width=
"80"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
:preview-src-list=
"[row.picUrl]"
class=
"h-30px w-30px"
preview-teleported
/>
</
template
>
</el-table-column>
<el-table-column
label=
"商品名称"
prop=
"name"
min-width=
"200"
:show-overflow-tooltip=
"true"
/>
<el-table-column
label=
"浏览量"
prop=
"browseCount"
min-width=
"90"
sortable=
"custom"
/>
<el-table-column
label=
"访客数"
prop=
"browseUserCount"
min-width=
"90"
sortable=
"custom"
/>
<el-table-column
label=
"加购件数"
prop=
"cartCount"
min-width=
"105"
sortable=
"custom"
/>
<el-table-column
label=
"下单件数"
prop=
"orderCount"
min-width=
"105"
sortable=
"custom"
/>
<el-table-column
label=
"支付件数"
prop=
"orderPayCount"
min-width=
"105"
sortable=
"custom"
/>
<el-table-column
label=
"支付金额"
prop=
"orderPayPrice"
min-width=
"105"
sortable=
"custom"
/>
<el-table-column
label=
"收藏数"
prop=
"favoriteCount"
min-width=
"90"
sortable=
"custom"
/>
<el-table-column
label=
"访客-支付转化率(%)"
prop=
"browseConvertPercent"
min-width=
"180"
sortable=
"custom"
:formatter=
"formatConvertRate"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
@
pagination=
"getSpuList"
/>
</el-card>
</template>
<
script
lang=
"ts"
setup
>
import
{
ProductStatisticsApi
,
ProductStatisticsVO
}
from
'@/api/mall/statistics/product'
import
{
CardTitle
}
from
'@/components/Card'
import
{
buildSortingField
}
from
'@/utils'
/** 商品排行 */
defineOptions
({
name
:
'ProductRank'
})
// 格式化:访客-支付转化率
const
formatConvertRate
=
(
row
:
ProductStatisticsVO
)
=>
{
return
`
${
row
.
browseConvertPercent
}
%`
}
const
handleSortChange
=
(
params
:
any
)
=>
{
queryParams
.
sortingFields
=
[
buildSortingField
(
params
)]
getSpuList
()
}
const
handleDateRangeChange
=
(
times
:
any
[])
=>
{
queryParams
.
times
=
times
as
[]
getSpuList
()
}
const
shortcutDateRangePicker
=
ref
()
// 查询参数
const
queryParams
=
reactive
({
pageNo
:
1
,
pageSize
:
10
,
times
:
[],
sortingFields
:
{}
})
// 列表的加载中
const
loading
=
ref
(
false
)
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的数据
const
list
=
ref
<
ProductStatisticsVO
[]
>
([])
/** 查询商品列表 */
const
getSpuList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
ProductStatisticsApi
.
getProductStatisticsRankPage
(
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
loading
.
value
=
false
}
}
/** 初始化 **/
onMounted
(
async
()
=>
{
await
getSpuList
()
})
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/mall/statistics/product/components/ProductSummary.vue
0 → 100644
View file @
ca35e1a4
<
template
>
<el-card
shadow=
"never"
>
<template
#
header
>
<!-- 标题 -->
<div
class=
"flex flex-row items-center justify-between"
>
<CardTitle
title=
"商品概况"
/>
<!-- 查询条件 -->
<ShortcutDateRangePicker
ref=
"shortcutDateRangePicker"
@
change=
"getProductTrendData"
>
<el-button
class=
"ml-4"
@
click=
"handleExport"
:loading=
"exportLoading"
v-hasPermi=
"['statistics:product:export']"
>
<Icon
icon=
"ep:download"
class=
"mr-1"
/>
导出
</el-button>
</ShortcutDateRangePicker>
</div>
</
template
>
<!-- 统计值 -->
<el-row
:gutter=
"16"
>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"商品浏览量"
tooltip=
"在选定条件下,所有商品详情页被访问的次数,一个人在统计时间内访问多次记为多次"
icon=
"ep:view"
icon-color=
"bg-blue-100"
icon-bg-color=
"text-blue-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.browseCount || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.browseCount,
trendSummary?.reference?.browseCount
)
"
/>
</el-col>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"商品访客数"
tooltip=
"在选定条件下,访问任何商品详情页的人数,一个人在统计时间范围内访问多次只记为一个"
icon=
"ep:user-filled"
icon-color=
"bg-purple-100"
icon-bg-color=
"text-purple-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.browseUserCount || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.browseUserCount,
trendSummary?.reference?.browseUserCount
)
"
/>
</el-col>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"支付件数"
tooltip=
"在选定条件下,成功付款订单的商品件数之和"
icon=
"fa-solid:money-check-alt"
icon-color=
"bg-yellow-100"
icon-bg-color=
"text-yellow-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.orderPayCount || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.orderPayCount,
trendSummary?.reference?.orderPayCount
)
"
/>
</el-col>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"支付金额"
tooltip=
"在选定条件下,成功付款订单的商品金额之和"
icon=
"ep:warning-filled"
icon-color=
"bg-green-100"
icon-bg-color=
"text-green-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.orderPayPrice || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.orderPayPrice,
trendSummary?.reference?.orderPayPrice
)
"
/>
</el-col>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"退款件数"
tooltip=
"在选定条件下,成功退款的商品件数之和"
icon=
"fa-solid:wallet"
icon-color=
"bg-cyan-100"
icon-bg-color=
"text-cyan-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.afterSaleCount || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.afterSaleCount,
trendSummary?.reference?.afterSaleCount
)
"
/>
</el-col>
<el-col
:xl=
"4"
:md=
"8"
:sm=
"24"
>
<SummaryCard
title=
"退款金额"
tooltip=
"在选定条件下,成功退款的商品金额之和"
icon=
"fa-solid:award"
icon-color=
"bg-yellow-100"
icon-bg-color=
"text-yellow-500"
prefix=
"¥"
:decimals=
"2"
:value=
"fenToYuan(trendSummary?.value?.afterSaleRefundPrice || 0)"
:percent=
"
calculateRelativeRate(
trendSummary?.value?.afterSaleRefundPrice,
trendSummary?.reference?.afterSaleRefundPrice
)
"
/>
</el-col>
</el-row>
<!-- 折线图 -->
<el-skeleton
:loading=
"trendLoading"
animated
>
<Echart
:height=
"500"
:options=
"lineChartOptions"
/>
</el-skeleton>
</el-card>
</template>
<
script
lang=
"ts"
setup
>
import
{
ProductStatisticsApi
,
ProductStatisticsVO
}
from
'@/api/mall/statistics/product'
import
SummaryCard
from
'@/components/SummaryCard/index.vue'
import
{
EChartsOption
}
from
'echarts'
import
{
DataComparisonRespVO
}
from
'@/api/mall/statistics/common'
import
{
calculateRelativeRate
,
fenToYuan
}
from
'@/utils'
import
download
from
'@/utils/download'
import
{
CardTitle
}
from
'@/components/Card'
import
*
as
DateUtil
from
'@/utils/formatTime'
import
dayjs
from
'dayjs'
/** 商品概况 */
defineOptions
({
name
:
'ProductSummary'
})
const
message
=
useMessage
()
// 消息弹窗
const
trendLoading
=
ref
(
true
)
// 商品状态加载中
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
trendSummary
=
ref
<
DataComparisonRespVO
<
ProductStatisticsVO
>>
()
// 商品状况统计数据
const
shortcutDateRangePicker
=
ref
()
/** 折线图配置 */
const
lineChartOptions
=
reactive
<
EChartsOption
>
({
dataset
:
{
dimensions
:
[
'time'
,
'browseCount'
,
'browseUserCount'
,
'orderPayPrice'
,
'afterSaleRefundPrice'
],
source
:
[]
},
grid
:
{
left
:
20
,
right
:
20
,
bottom
:
20
,
top
:
80
,
containLabel
:
true
},
legend
:
{
top
:
50
},
series
:
[
{
name
:
'商品浏览量'
,
type
:
'line'
,
smooth
:
true
,
itemStyle
:
{
color
:
'#B37FEB'
}
},
{
name
:
'商品访客数'
,
type
:
'line'
,
smooth
:
true
,
itemStyle
:
{
color
:
'#FFAB2B'
}
},
{
name
:
'支付金额'
,
type
:
'bar'
,
smooth
:
true
,
yAxisIndex
:
1
,
itemStyle
:
{
color
:
'#1890FF'
}
},
{
name
:
'退款金额'
,
type
:
'bar'
,
smooth
:
true
,
yAxisIndex
:
1
,
itemStyle
:
{
color
:
'#00C050'
}
}
],
toolbox
:
{
feature
:
{
// 数据区域缩放
dataZoom
:
{
yAxisIndex
:
false
// Y轴不缩放
},
brush
:
{
type
:
[
'lineX'
,
'clear'
]
// 区域缩放按钮、还原按钮
},
saveAsImage
:
{
show
:
true
,
name
:
'商品状况'
}
// 保存为图片
}
},
tooltip
:
{
trigger
:
'axis'
,
axisPointer
:
{
type
:
'cross'
},
padding
:
[
5
,
10
]
},
xAxis
:
{
type
:
'category'
,
boundaryGap
:
true
,
axisTick
:
{
show
:
false
}
},
yAxis
:
[
{
type
:
'value'
,
name
:
'金额'
,
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
axisLabel
:
{
textStyle
:
{
color
:
'#7F8B9C'
}
},
splitLine
:
{
show
:
true
,
lineStyle
:
{
color
:
'#F5F7F9'
}
}
},
{
type
:
'value'
,
name
:
'数量'
,
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
axisLabel
:
{
textStyle
:
{
color
:
'#7F8B9C'
}
},
splitLine
:
{
show
:
true
,
lineStyle
:
{
color
:
'#F5F7F9'
}
}
}
]
})
as
EChartsOption
/** 处理商品状况查询 */
const
getProductTrendData
=
async
()
=>
{
trendLoading
.
value
=
true
// 1. 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
const
times
=
shortcutDateRangePicker
.
value
.
times
if
(
DateUtil
.
isSameDay
(
times
[
0
],
times
[
1
]))
{
// 前天
times
[
0
]
=
DateUtil
.
formatDate
(
dayjs
(
times
[
0
]).
subtract
(
1
,
'd'
))
}
// 查询数据
await
Promise
.
all
([
getProductTrendSummary
(),
getProductStatisticsList
()])
trendLoading
.
value
=
false
}
/** 查询商品状况数据统计 */
const
getProductTrendSummary
=
async
()
=>
{
const
times
=
shortcutDateRangePicker
.
value
.
times
trendSummary
.
value
=
await
ProductStatisticsApi
.
getProductStatisticsAnalyse
({
times
})
}
/** 查询商品状况数据列表 */
const
getProductStatisticsList
=
async
()
=>
{
// 查询数据
const
times
=
shortcutDateRangePicker
.
value
.
times
const
list
:
ProductStatisticsVO
[]
=
await
ProductStatisticsApi
.
getProductStatisticsList
({
times
})
// 处理数据
for
(
let
item
of
list
)
{
item
.
orderPayPrice
=
fenToYuan
(
item
.
orderPayPrice
)
item
.
afterSaleRefundPrice
=
fenToYuan
(
item
.
afterSaleRefundPrice
)
}
// 更新 Echarts 数据
if
(
lineChartOptions
.
dataset
&&
lineChartOptions
.
dataset
[
'source'
])
{
lineChartOptions
.
dataset
[
'source'
]
=
list
}
}
/** 导出按钮操作 */
const
handleExport
=
async
()
=>
{
try
{
// 导出的二次确认
await
message
.
exportConfirm
()
// 发起导出
exportLoading
.
value
=
true
const
times
=
shortcutDateRangePicker
.
value
.
times
const
data
=
await
ProductStatisticsApi
.
exportProductStatisticsExcel
({
times
})
download
.
excel
(
data
,
'商品状况.xls'
)
}
catch
{
}
finally
{
exportLoading
.
value
=
false
}
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/mall/statistics/product/index.vue
0 → 100644
View file @
ca35e1a4
<
template
>
<!-- 商品概览 -->
<ProductSummary
/>
<!-- 商品排行 -->
<ProductRank
class=
"mt-16px"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
ProductSummary
from
'./components/ProductSummary.vue'
import
ProductRank
from
'./components/ProductRank.vue'
/** 商品统计 */
defineOptions
({
name
:
'ProductStatistics'
})
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/mall/statistics/trade/index.vue
View file @
ca35e1a4
...
@@ -298,7 +298,7 @@ const getTradeTrendData = async () => {
...
@@ -298,7 +298,7 @@ const getTradeTrendData = async () => {
times
[
0
]
=
DateUtil
.
formatDate
(
dayjs
(
times
[
0
]).
subtract
(
1
,
'd'
))
times
[
0
]
=
DateUtil
.
formatDate
(
dayjs
(
times
[
0
]).
subtract
(
1
,
'd'
))
}
}
// 查询数据
// 查询数据
await
Promise
.
all
([
getTrade
TrendSummary
(),
getTradeStatisticsList
()])
await
Promise
.
all
([
getTrade
StatisticsAnalyse
(),
getTradeStatisticsList
()])
trendLoading
.
value
=
false
trendLoading
.
value
=
false
}
}
...
@@ -308,9 +308,9 @@ const getTradeStatisticsSummary = async () => {
...
@@ -308,9 +308,9 @@ const getTradeStatisticsSummary = async () => {
}
}
/** 查询交易状况数据统计 */
/** 查询交易状况数据统计 */
const
getTrade
TrendSummary
=
async
()
=>
{
const
getTrade
StatisticsAnalyse
=
async
()
=>
{
const
times
=
shortcutDateRangePicker
.
value
.
times
const
times
=
shortcutDateRangePicker
.
value
.
times
trendSummary
.
value
=
await
TradeStatisticsApi
.
getTrade
TrendSummary
({
times
})
trendSummary
.
value
=
await
TradeStatisticsApi
.
getTrade
StatisticsAnalyse
({
times
})
}
}
/** 查询交易状况数据列表 */
/** 查询交易状况数据列表 */
...
...
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