Files
cashier-admin/src/views/home/home.vue
2024-03-16 17:47:15 +08:00

829 lines
22 KiB
Vue

<template>
<div class="app-container" style="padding-bottom: 100px;">
<div class="card_wrap">
<div class="card">
<div class="header">
<div class="card_title">总销售额</div>
<el-tooltip effect="dark" content="订单支付金额" placement="top">
<i class="icon el-icon-warning-outline"></i>
</el-tooltip>
</div>
<div class="number">{{ topData.totalSales || 0 }}</div>
<div class="row">平均每单{{ topData.averageSales || 0 }}</div>
<div class="row">今日销售额{{ topData.totalSalesToday || 0 }}</div>
</div>
<div class="card">
<div class="header">
<div class="card_title">支付笔数</div>
</div>
<div class="number">{{ topData.paymentsNumber }}</div>
<div class="row" ref="cardPayChart" style="padding-bottom: 2px;"></div>
<div class="row">今日支付笔数{{ topData.paymentsNumberToday || 0 }}</div>
</div>
<div class="card">
<div class="header">
<div class="card_title">访问量</div>
</div>
<div class="number">{{ topData.totalVisits }}</div>
<div class="row" ref="cardCountChart"></div>
<div class="row">
<div class="dot"></div> 今日访问 {{ topData.totalVisitsToday || 0 }}
</div>
</div>
<div class="card">
<div class="header">
<div class="card_title">用户数</div>
</div>
<div class="number">{{ topData.totalUser }}</div>
<div class="row" ref="cardUserChart" style="padding-bottom: 2px;"></div>
<div class="row">今日新增 {{ topData.userToday || 0 }} <i class="icon el-icon-caret-top"></i> </div>
</div>
</div>
<!-- 销售额 -->
<div class="chart_wrap">
<div class="item">
<div class="header">
<div class="tab_wrap">
<div class="item active">销售额</div>
</div>
<el-radio-group v-model="saleActive" @change="dateAmount">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">30</el-radio-button>
</el-radio-group>
</div>
<div class="chart" ref="saleChart" v-loading="saleLoading" style="height: 400px;">
</div>
</div>
</div>
<div class="chart_wrap" style="display: flex;">
<!-- 商品销售排行 -->
<div class="item">
<div class="header">
<div class="tab_wrap">
<div class="item active">商品销售排行</div>
</div>
<el-radio-group v-model="saleTableActive" @change="rankChange">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">30</el-radio-button>
</el-radio-group>
</div>
<div class="sale_data">
<div class="card">
<div class="sale_data_header">
<div class="card_title">销售数量</div>
</div>
<div class="number">{{ productCount }}</div>
<div class="product_chart_wrap" ref="productCountChart"></div>
</div>
<div class="card">
<div class="sale_data_header">
<div class="card_title">销售金额</div>
</div>
<div class="number">{{ productSum }}</div>
<div class="product_chart_wrap" ref="productSumChart"></div>
</div>
</div>
<div class="table">
<el-table :data="saleTable" v-loading="saleTableLoading">
<el-table-column label="排名" prop="productId"></el-table-column>
<el-table-column label="商品名称" prop="productName"></el-table-column>
<el-table-column label="数量" prop="productNum"></el-table-column>
<el-table-column label="金额" prop="amount"></el-table-column>
</el-table>
<div class="head-container" style="padding-top: 20px;display: flex;justify-content: flex-end;">
<el-pagination :total="saleTableTotal" :page-size="saleTableSize" :current-page="saleTablePage"
@current-change="paginationChange" layout="total, prev, pager, next, jumper"></el-pagination>
</div>
</div>
</div>
<!-- 支付类型占比 -->
<div class="item" style="margin-left: 20px;">
<div class="header">
<div class="tab_wrap">
<div class="item active">支付占比类型</div>
</div>
<el-radio-group v-model="payChartDay" @change="datePayType">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">30</el-radio-button>
</el-radio-group>
</div>
<div style="height: 400px;margin-top: 30px;" ref="payChart" v-loading="payChartLoading"></div>
</div>
</div>
</div>
</template>
<script>
import { summaryGet, summaryTodayGet, dateProduct, dateAmount, datePayType, summaryDateGet } from '@/api/home'
import echarts from 'echarts'
import { debounce } from '@/utils'
export default {
name: 'home',
data() {
return {
topData: '',
saleTab: 'sale',
saleActive: '7',
cardPayChart: null,
cardCountChart: null,
cardUserChart: null,
saleLoading: false,
saleChart: null,
payChartDay: '7',
payChartLoading: false,
payChart: null,
chartType: 1,
productCount: 0,
productSum: 0,
saleTableActive: '7',
saleTable: [],
saleTableLoading: false,
saleTablePage: 1,
saleTableTotal: 0,
saleTableSize: 5,
__resizeHandler: null,
productCountChart: null,
productSumChart: null
}
},
mounted() {
this.summaryGet()
this.dateAmount()
this.dateProduct()
this.datePayType()
this.summaryDateGet()
this.__resizeHandler = debounce(() => {
if (this.saleChart) {
this.saleChart.resize()
}
if (this.payChart) {
this.payChart.resize()
}
if (this.cardPayChart) {
this.cardPayChart.resize()
}
if (this.cardUserChart) {
this.cardUserChart.resize()
}
if (this.productCountChart) {
this.productCountChart.resize()
}
if (this.productSumChart) {
this.productSumChart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
this.initCardUserChart()
},
methods: {
// 初始化支付笔数柱状图
initCardPayChart(time = [], data = []) {
this.cardPayChart = echarts.init(this.$refs.cardPayChart)
this.cardPayChart.setOption({
tooltip: {
trigger: 'axis'
},
grid: {
x: 0,
y: 0,
x2: 0,
y2: 0
},
xAxis: [{
type: 'category',
data: time,
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}
],
series: [
{
data: data,
type: 'bar',
barWidth: '30%'
}
]
})
},
// 初始化访问量柱状图
initCardCountChart(time = [], data = []) {
this.cardCountChart = echarts.init(this.$refs.cardCountChart)
this.cardCountChart.setOption({
tooltip: {
trigger: 'axis'
},
grid: {
x: 0,
y: 0,
x2: 0,
y2: 0
},
xAxis: [{
type: 'category',
data: time,
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}
],
series: [
{
data: data,
type: 'bar',
barWidth: '30%'
}
]
})
},
// 初始化用户数折线图
initCardUserChart(time = [], data = []) {
this.cardUserChart = echarts.init(this.$refs.cardUserChart)
this.cardUserChart.setOption({
tooltip: {
trigger: 'axis'
},
grid: {
x: 0,
y: 10,
x2: 0,
y2: 2
},
xAxis: [{
type: 'category',
data: time,
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}
],
series: [
{
data: data,
type: 'line',
symbol: 'none'
}
]
})
},
// 初始化销售额图标
initSaleChart(time, data) {
this.saleChart = null
this.saleChart = echarts.init(this.$refs.saleChart)
this.saleChart.setOption({
title: {
text: '销售趋势',
x: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: [{
type: 'category',
data: time,
axisTick: {
alignWithLabel: true
},
axisLine: {
lineStyle: {
color: '#999'
}
},
axisLabel: {
rotate: time.length <= 7 ? 0 : 45,
interval: 0,
textStyle: {
fontSize: '9'
}
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999'
}
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#ececec'
}
}
}
],
series: [
{
data: data,
type: 'bar',
barWidth: time.length <= 7 ? '50%' : '30%'
}
]
})
},
// 初始化销售额图表
initPayChart(data) {
this.payChart = echarts.init(this.$refs.payChart)
this.payChart.setOption({
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
color: ['#409eff', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
series: [
{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20
}
},
labelLine: {
show: false
},
data: data
}
]
})
},
// 获取销售额柱状图数据
async dateAmount() {
try {
this.saleLoading = true
const res = await dateAmount(this.saleActive)
const data = res.total.map(item => item.amount)
const time = res.total.map(item => item.tradeDay)
this.initSaleChart(time, data)
setTimeout(() => {
this.saleLoading = false
}, 300);
} catch (error) {
console.log(error)
}
},
paginationChange(e) {
this.saleTablePage = e
this.dateProduct()
},
// 获取销售额排行表格数据
async dateProduct() {
try {
this.saleTableLoading = true
const res = await dateProduct(this.saleTableActive, this.saleTablePage, this.saleTableSize)
this.saleTable = res.totalProduct
this.saleTableTotal = res.total
this.productCount = res.productCount
this.productSum = res.productSum
setTimeout(() => {
this.saleTableLoading = false
}, 300);
} catch (error) {
console.log(error)
}
},
// 支付类型占比 饼图
async datePayType() {
try {
this.payChartLoading = true
const res = await datePayType(this.payChartDay)
const data = res.countPayType.map(item => {
return {
value: item.count,
name: item.payType
}
})
setTimeout(() => {
this.payChartLoading = false
}, 300);
this.initPayChart(data)
} catch (error) {
console.log(error)
}
},
// 汇总数据
async summaryGet() {
try {
const res1 = await summaryGet()
const res2 = await summaryTodayGet()
this.topData = {
...res1,
...res2
}
let payTime = res1.countDateList.map(item => item.tradeDay)
let payData = res1.countDateList.map(item => item.count)
let countTime = res1.visitsCountList.map(item => item.tradeDay)
let countData = res1.visitsCountList.map(item => item.count)
this.initCardPayChart(payTime, payData)
this.initCardCountChart(countTime, countData)
console.log(this.topData)
} catch (error) {
console.log(error)
}
},
rankChange() {
this.dateProduct()
this.summaryDateGet()
},
// 初始化销售图标
initProduceChart(p1, p2) {
this.productCountChart = echarts.init(this.$refs.productCountChart)
this.productSumChart = echarts.init(this.$refs.productSumChart)
this.productCountChart.setOption({
tooltip: {
trigger: 'axis'
},
grid: {
x: 0,
y: 0,
x2: 0,
y2: 0
},
xAxis: [{
boundaryGap: false,
type: 'category',
data: p1[0],
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}
],
series: [
{
data: p1[1],
type: 'line',
symbol: 'none',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#409eff' // 渐变颜色
}, {
offset: 1,
color: '#409eff' // 渐变颜色
}])
}
}
]
})
this.productSumChart.setOption({
tooltip: {
trigger: 'axis'
},
grid: {
x: 0,
y: 0,
x2: 0,
y2: 0
},
xAxis: [{
boundaryGap: false,
type: 'category',
data: p2[0],
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}],
color: '#409eff',
yAxis: [
{
type: 'value',
show: false, // 不显示坐标轴线、坐标轴刻度线和坐标轴上的文字
axisTick: {
show: false // 不显示坐标轴刻度线
},
axisLine: {
show: false, // 不显示坐标轴线
},
axisLabel: {
show: false, // 不显示坐标轴上的文字
},
splitLine: {
show: false // 不显示网格线
}
}
],
series: [
{
data: p2[1],
type: 'line',
symbol: 'none',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#409eff' // 渐变颜色
}, {
offset: 1,
color: '#409eff' // 渐变颜色
}])
}
}
]
})
},
// 日期汇总数据
async summaryDateGet() {
try {
const res = await summaryDateGet(this.saleTableActive)
let p1 = [res.numList.map(item => item.tradeDay), res.numList.map(item => item.count)]
let p2 = [res.amountList.map(item => item.tradeDay), res.amountList.map(item => item.count)]
this.initProduceChart(p1, p2)
} catch (error) {
console.log(error);
}
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
background-color: #F5F5F5;
}
.card_wrap {
display: flex;
flex-wrap: wrap;
gap: 20px;
.card {
flex: 1;
background-color: #fff;
border-radius: 2px;
padding: 0 20px;
.header {
display: flex;
justify-content: space-between;
color: #999;
padding-top: 20px;
.card_title {
font-size: 14px;
flex: 1;
}
}
.number {
padding: 20px 0 10px 0;
font-size: 24px;
height: 60px;
}
.row {
height: 50px;
color: #555;
font-size: 14px;
display: flex;
align-items: center;
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.icon {
color: rgb(255, 85, 85);
margin-left: 4px;
}
.dot {
$size: 6px;
width: $size;
height: $size;
border-radius: 50%;
background-color: #1890FF;
margin-right: 6px;
}
}
}
}
.chart_wrap {
margin-top: 20px;
.sale_data {
display: flex;
.card {
flex: 1;
background-color: #fff;
border-radius: 2px;
padding: 0 20px;
.sale_data_header {
display: flex;
justify-content: space-between;
color: #999;
padding-top: 20px;
.card_title {
font-size: 14px;
flex: 1;
}
}
.number {
padding-top: 10px;
font-size: 24px;
height: 60px;
}
.product_chart_wrap {
height: 50px;
}
}
}
.item {
flex: 1;
background-color: #fff;
border-radius: 2px;
.header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ececec;
padding: 0 20px;
.tab_wrap {
display: flex;
$color: #1890FF;
.item {
padding: 0 10px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: $color;
&.active {
position: relative;
&::after {
content: "";
width: 100%;
border-bottom: 2px solid $color;
position: absolute;
bottom: 0;
left: 0;
}
}
}
}
}
.chart {
padding: 20px 0;
height: 300px;
}
.table {
padding: 20px;
}
}
}
</style>