Files
temu-plugin/src/view/sale/ExportSaleStatTemu.vue
liushiwei bbde4e3f3f 调整
2024-10-30 09:42:08 +08:00

890 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="销售统计"
tips="最多只能统计近60天的销售数据"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">时间范围</label>
<el-date-picker
v-model="dateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
<el-button type="button" :class="'el-button el-button--primary'" @click="toBeginStat">开始统计</el-button>
<el-button type="primary" icon="el-icon-download" @click="downloadPicture">下载图片</el-button>
<json-excel
:data="skcList"
:fields="skcJsonFields"
name="SKC销售统计.xls"
worksheet="SKC销售统计">
<el-button type="primary" icon="el-icon-download">下载SKC统计</el-button>
</json-excel>
<json-excel
:data="skuList"
:fields="skuJsonFields"
name="SKU销售统计.xls"
worksheet="SKU销售统计">
<el-button type="primary" icon="el-icon-download">下载SKU统计</el-button>
</json-excel>
</div>
</template>
</ai-title>
<template slot="content">
<div id="app-content">
<ai-card title="数据概览" style="padding-bottom: 40px;">
<div>
<el-row :gutter="20">
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="saleAmount"
title="销售额"
>
</el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:value="saleCount"
title="单量"
>
</el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="costAmount"
title="成本"
></el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="profitAmount"
title="利润"
></el-statistic>
</div>
</el-col>
<el-col :span="4">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="profitPercent"
title="利润率"
>
<template slot="suffix">%</template>
</el-statistic>
</div>
</el-col>
</el-row>
</div>
</ai-card>
<ai-card title="趋势分析" style="padding-bottom: 40px;">
<div>
<div id="chart1"></div>
</div>
</ai-card>
<div>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKC销售额TOP10排行">
<ai-table
ref="table0"
:isShowPagination="false"
:tableData="topSaleAmountSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKC利润TOP10排行">
<ai-table
ref="table1"
:isShowPagination="false"
:tableData="topSaleProfitSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKC单量TOP10排行">
<ai-table
ref="table2"
:isShowPagination="false"
:tableData="topSaleCountSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKU销售额TOP10排行">
<ai-table
ref="table3"
:isShowPagination="false"
:tableData="topSaleAmountSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKU利润TOP10排行">
<ai-table
ref="table4"
:isShowPagination="false"
:tableData="topSaleProfitSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKU单量TOP10排行">
<ai-table
ref="table5"
:isShowPagination="false"
:tableData="topSaleCountSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
</div>
</div>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage } from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel'
import { Message } from 'element-ui'
import {formatDate} from '@/utils/date'
import html2canvas from 'html2canvas'
import { DualAxes } from '@antv/g2plot'
export default {
name: 'ExportSaleStatTemu',
data () {
return {
mallId: null,
storeCode: '',
dateRange: null,
currentPage: 1,
pageSize: 50,
productList: [],
productCostList: [],
saleAmount: 0.0,
saleCount: 0,
costAmount: 0.0,
profitAmount: 0.0,
profitPercent: 0.0,
deductionAmount: 0.0,
skcList: [],
skuList: [],
leftChartData: [],
rightChartData: [],
isLoading: false,
skcColConfigs: [
{ prop: 'skcExtCode', label: 'SKC货号', width: '250px' },
{ slot: 'saleAmount', label: '销售额', align: 'center' },
{ slot: 'saleCount', label: '单量', align: 'center' },
{ prop: 'costAmount', label: '成本', align: 'center'},
{ slot: 'profitAmount', label: '利润', align: 'center' }
],
skcJsonFields: {
"SKC": "productSkcId",
"SKC货号": "skcExtCode",
"销售额": "saleAmount",
"单量": "saleCount",
"成本": "costAmount",
"利润": "profitAmount"
},
skuColConfigs: [
{ slot: 'skuExtCode', label: 'SKC货号/SKU货号' },
{ slot: 'saleAmount', label: '销售额', align: 'center' },
{ slot: 'saleCount', label: '单量', align: 'center' },
{ prop: 'costAmount', label: '成本', align: 'center'},
{ slot: 'profitAmount', label: '利润', align: 'center' }
],
skuJsonFields: {
"SKC": "productSkcId",
"SKU": "productSkuId",
"SKC货号": "skcExtCode",
"SKU货号": "skuExtCode",
"销售额": "saleAmount",
"单量": "saleCount",
"成本": "costAmount",
"利润": "profitAmount"
},
chartObj: null
}
},
computed: {
topSaleAmountSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.saleAmount - a.saleAmount)
return list.slice(0, 10)
},
topSaleProfitSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.profitAmount - a.profitAmount)
return list.slice(0, 10)
},
topSaleCountSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.saleCount - a.saleCount)
return list.slice(0, 10)
},
topSaleAmountSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.saleAmount - a.saleAmount)
return list.slice(0, 10)
},
topSaleProfitSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.profitAmount - a.profitAmount)
return list.slice(0, 10)
},
topSaleCountSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.saleCount - a.saleCount)
return list.slice(0, 10)
}
},
components: {
JsonExcel
},
mounted () {
this.initChart1()
},
methods: {
initChart1 () {
this.chartObj = new DualAxes('chart1', {
data: [this.leftChartData, this.rightChartData],
xField: 'day',
yField: ['value', 'value1'],
yAxis: {
value: {
title: {
text: '金额'
},
label: {
formatter: (value) => {
return `${value}`;
}
}
},
value1: {
max: 100,
title: {
text: '利润率'
},
label: {
formatter: (value) => {
return `${value}%`;
}
}
}
},
geometryOptions: [
{
geometry: 'line',
color: ['#FF9A49', '#7A8AA1', '#1EEB73', '#C947AE' ],
isGroup: true,
smooth: true, // 是否平滑
seriesField: 'type'
},
{
geometry: 'line',
color: ['red' ],
lineStyle: {
fill: 'red',
fillOpacity: 0.5,
stroke: 'red',
lineWidth: 4,
strokeOpacity: 0.7,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 5,
shadowOffsetY: 5,
cursor: 'pointer'
},
isStack: true,
smooth: true, // 是否平滑
seriesField: 'type', // 指定使用第二个Y轴字段
}],
});
this.chartObj.render();
},
toBeginStat() {
if (!this.dateRange) {
Message.error("请选择统计时间范围")
return
}
if (!this.mallId) {
Message.error("请选择店铺")
return
}
this.$userCheck(this.mallId).then(() => {
this.beginStat()
}).catch((err) => {
this.mallId = ''
this.isLoading = false
})
},
async beginStat() {
this.currentPage = 1
this.isLoading = true
this.saleAmount = 0.0
this.saleCount = 0
this.costAmount = 0.0
this.profitAmount = 0.0
this.profitPercent = 0.0
this.productList = []
this.productCostList = []
this.skcList = []
this.skuList = []
this.leftChartData = []
this.rightChartData = []
this.chartObj.changeData([this.leftChartData, this.rightChartData])
await this.getProductList()
await this.getSkuCostList()
await this.getSkuSaleNumber(0)
this.leftChartData.sort((a, b) => {
let leftDate = new Date(a.day)
let rightDate = new Date(b.day)
return leftDate.getTime() - rightDate.getTime()
})
this.leftChartData.map(item => {
if (item.type == '销售额' || item.type == '成本' || item.type == '利润') {
item.value = Math.round(item.value * 100) / 100
}
if (item.type == '销售额') {
this.leftChartData.map(item1 => {
if (item1.type == '成本' && item1.day == item.day) {
this.rightChartData.push({
day: item1.day,
value1: Math.round((item.value - item1.value) / item.value * 10000) / 100,
type: '利润率'
})
}
})
}
})
this.rightChartData.sort((a, b) => {
let leftDate = new Date(a.day)
let rightDate = new Date(b.day)
return leftDate.getTime() - rightDate.getTime()
})
this.skuList = this.skuList.filter(item => {
return item.saleCount > 0
})
this.skuList.map(item => {
item.saleAmount = Math.round(item.saleAmount * 100) / 100
item.costAmount = Math.round(item.costAmount * 100) / 100
item.profitAmount = Math.round(item.profitAmount * 100) / 100
})
this.skuList.map(item => {
let flag = false
for (let i = 0; i < this.skcList.length; i++) {
if (item.productSkcId == this.skcList[i].productSkcId) {
this.skcList[i].saleAmount = this.skcList[i].saleAmount + item.saleAmount
this.skcList[i].costAmount = this.skcList[i].costAmount + item.costAmount
this.skcList[i].saleCount = this.skcList[i].saleCount + item.saleCount
this.skcList[i].profitAmount = this.skcList[i].profitAmount + item.profitAmount
flag = true
break
}
}
if (!flag) {
this.skcList.push({
productSkcId: item.productSkcId,
skcExtCode: item.skcExtCode,
saleAmount: item.saleAmount,
saleCount: item.saleCount,
costAmount: item.costAmount,
profitAmount: item.profitAmount,
profitPercent: item.profitPercent
})
}
})
this.skcList.map(item => {
item.saleAmount = Math.round(item.saleAmount * 100) / 100
item.costAmount = Math.round(item.costAmount * 100) / 100
item.profitAmount = Math.round(item.profitAmount * 100) / 100
})
this.leftChartData.map(item => {
if (item.type == '销售额') {
this.saleAmount = this.saleAmount + item.value
}
if (item.type == '成本') {
this.costAmount = this.costAmount + item.value
}
if (item.type == '利润') {
this.profitAmount = this.profitAmount + item.value
}
if (item.type == '单量') {
this.saleCount = this.saleCount + item.value
}
})
this.saleAmount = Math.round(this.saleAmount * 100) / 100
this.costAmount = Math.round(this.costAmount * 100) / 100
this.profitAmount = Math.round(this.profitAmount * 100) / 100
this.profitPercent = Math.round((this.saleAmount - this.costAmount) / this.saleAmount * 10000) / 100
this.chartObj.changeData([this.leftChartData, this.rightChartData])
this.isLoading = false
},
getPorductList() {
this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.storeCode
}
}).then(res => {
if (res.code == 0) {
this.productList = res.data
}
})
},
async getProductList() {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/listWarehouse',
needMallId: true,
mallId: this.mallId,
data: {
pageNo: this.currentPage,
pageSize: this.pageSize,
isLack: 0,
priceAdjustRecentDays: 7
}})
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.subOrderList.length; i++) {
let item = res.result.subOrderList[i]
let data = {}
data.productName = item.productName
data.productId = item.productId
data.productSkcId = item.productSkcId
data.skcExtCode = item.skcExtCode
for(let j = 0;j < item.skuQuantityDetailList.length; j++) {
data = {...data,
skuExtCode: item.skuQuantityDetailList[j].skuExtCode,
supplierPrice: item.skuQuantityDetailList[j].supplierPrice / 100,
productSkuId: item.skuQuantityDetailList[j].productSkuId,
className: item.skuQuantityDetailList[j].className
}
this.productList.push(data)
}
}
if ((this.currentPage * this.pageSize) < res.result.total) {
this.currentPage ++
await this.getProductList()
}
} else {
await this.getProductList()
}
},
async getSkuSaleNumber (page) {
let tempSkuId = []
let i = page * 500
let j = 0
for (; i < this.productList.length; i++) {
tempSkuId.push(this.productList[i].productSkuId)
j ++
if (j == 500) break
}
if (tempSkuId.length == 0) return
let res = await sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/sales/management/querySkuSalesNumber',
needMallId: true,
mallId: this.mallId,
data: {
productSkuIds: tempSkuId,
startDate: this.dateRange[0],
endDate: this.dateRange[1]
}})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.length; i++) {
let cost = this.getCost(res.result[i].prodSkuId)
let flag = false
// 计算每日销售额
let skuObj = 0
for (let k = 0; k < this.productList.length; k++) {
if (this.productList[k].productSkuId == res.result[i].prodSkuId) {
skuObj = this.productList[k]
break
}
}
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '销售额' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + skuObj.supplierPrice * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '销售额',
value: skuObj.supplierPrice * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日销量
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '单量' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '单量',
value: res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日成本
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '成本' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + cost * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '成本',
value: cost * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日利润
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '利润' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + (skuObj.supplierPrice - cost) * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '利润',
value: (skuObj.supplierPrice - cost) * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算SKU维度销售额
flag = false
for (let j = 0; j < this.skuList.length; j++) {
if (this.skuList[j].productSkuId == res.result[i].prodSkuId) {
this.skuList[j].saleAmount = this.skuList[j].saleAmount + skuObj.supplierPrice * res.result[i].salesNumber
this.skuList[j].costAmount = this.skuList[j].costAmount + cost * res.result[i].salesNumber
this.skuList[j].saleCount = this.skuList[j].saleCount + res.result[i].salesNumber
this.skuList[j].profitAmount = this.skuList[j].profitAmount + (skuObj.supplierPrice - cost) * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.skuList.push({
productSkcId: skuObj.productSkcId,
productSkuId: skuObj.productSkuId,
skcExtCode: skuObj.skcExtCode,
skuExtCode: skuObj.skuExtCode,
saleAmount: skuObj.supplierPrice * res.result[i].salesNumber,
costAmount: cost * res.result[i].salesNumber,
profitAmount: (skuObj.supplierPrice - cost) * res.result[i].salesNumber,
saleCount: res.result[i].salesNumber,
profitPercent: Math.round((skuObj.supplierPrice - cost) / skuObj.supplierPrice * 10000) / 100
})
}
}
await this.getSkuSaleNumber(page + 1)
} else {
await this.getSkuSaleNumber(page)
}
},
async getSkuCostList() {
let res = await this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.mallId
}
})
if (res.code == 0) {
this.productCostList = res.data
}
},
getCost(sku) {
for (let i = 0; i < this.productCostList.length; i++) {
if (sku == this.productCostList[i].sku) {
return this.productCostList[i].costPrice
}
}
return 0
},
sleepSync(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
},
async downloadPicture() {
try {
const element = document.getElementById('app-content');
const canvas = await html2canvas(element);
// 创建一个图片元素
const img = new Image();
img.src = canvas.toDataURL('image/png');
// 添加到body中以便下载
document.body.appendChild(img);
// 触发下载
const a = document.createElement('a');
a.style.display = 'none'
a.href = img.src;
a.download = '销售统计.png';
a.click();
document.body.removeChild(img);
} catch (error) {
console.error('Error saving image:', error);
}
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
.top {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
.item {
flex: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
&:nth-of-type(1) {
color: #2266ff;
}
&:nth-of-type(2) {
color: #f8b426;
}
&:nth-of-type(3) {
color: #21aa99;
}
&:nth-of-type(4) {
color: #F46;
}
&:nth-of-type(5) {
color: #11A265;
}
h2 {
margin-bottom: 30px;
font-size: 16px;
color: #999;
}
p {
font-weight: 600;
font-size: 28px;
}
}
}
}
.like {
cursor: pointer;
font-size: 25px;
display: inline-block;
}
</style>