697 lines
23 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>
<div class="pageBox">
<div class="searchBox">
<div class="row row1">
<span class="span">商品名称</span>
<div class="right"><el-input v-model="filter.title" class="wid100" clearable></el-input></div>
</div>
<div class="row row1">
<span class="span">规格编码:</span>
<div class="right"><el-input v-model="filter.sku_code" class="wid100" clearable></el-input></div>
</div>
<div class="row row1">
<span class="span">商品品牌:</span>
<div class="right">
<el-select v-model="filter.brand_id" placeholder="请选择" clearable class="wid100">
<el-option v-for="it in brandList" :key="it.id" :label="it.name" :value="it.id" />
</el-select>
</div>
</div>
<div class="row row1">
<span class="span">赠品:</span>
<div class="right">
<el-select v-model="filter.gift" placeholder="请选择" :clearable="false" class="wid100">
<el-option label="全部" value="all" />
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</div>
</div>
<div class="row">
<span class="span">时间区间:</span>
<div class="right">
<el-date-picker
v-model="rangeTime"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
style="width: 250px;">
</el-date-picker>
</div>
</div>
<div class="row">
<span class="span"></span>
<el-button type="primary" @click="handleSearch"><el-icon><Search /></el-icon>&nbsp;筛选</el-button>
</div>
</div>
<el-card shadow="never">
<div class="opaBox">
<el-button type="warning" @click="handleExport" :loading="exportLoading"><span class="iconfont icon-daochu"></span>&nbsp;导出</el-button>
</div>
<el-table :data="statisticsList" style="width: 100%" border v-loading="loading" @sort-change="sortChange"
:default-sort="{
prop: 'number,order_num,seven_day_avg_number,stock_wait,three_day_avg_number,three_day_stock_wait,thirty_day_number,actual_inventory,total_profit',
}">
<el-table-column prop="date" label="日期" align="center" />
<el-table-column prop="type" label="商品信息" width="220">
<template #default="scope">
<div class="goodInfo" v-if="scope.row.goods_sku">
<div class="imgBox" v-if="scope.row.goods_sku.goods && scope.row.goods_sku.goods.images">
<el-image v-for="(it, i) in scope.row.goods_sku.goods.images" :key="i" :z-index="9999"
:src="it" :hide-on-click-modal="true" :preview-src-list="[scope.row.goods_sku.goods.images]"
fit="cover" :preview-teleported="true" />
</div>
<div class="tit">{{ scope.row.goods_sku.goods.title }}({{ scope.row.goods_sku.title }})</div>
</div>
</template>
</el-table-column>
<el-table-column prop="number" label="销量" align="center" sortable="custom" />
<el-table-column prop="order_num" label="单数" align="center" sortable="custom" />
<el-table-column prop="seven_day_avg_number" label="7天日销" align="center" sortable="custom" />
<el-table-column prop="stock_wait" label="7天周转天数" align="center" sortable="custom" />
<el-table-column prop="three_day_avg_number" label="3天日销" align="center" sortable="custom" />
<el-table-column prop="three_day_stock_wait" label="3天周转天数" align="center" sortable="custom" />
<el-table-column prop="thirty_day_number" label="近30天销量" align="center" sortable="custom" />
<el-table-column prop="actual_inventory" label="总库存" align="center" sortable="custom">
<template #header>
<span style="margin-right: 5px;">总库存</span>
<span>
<el-tooltip placement="top" :hide-after="0" :show-after="200">
<template #content>总库存包含未锁定和锁定库存,括号里为锁定库存</template>
<el-icon size="18"><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="scope">
<div v-if="scope.row.goods_sku">{{ scope.row.goods_sku.actual_inventory }}{{ scope.row.goods_sku.lock_in_stock }}</div>
</template>
</el-table-column>
<el-table-column prop="goods_cost" label="商品成本" align="center" />
<el-table-column prop="freight_cost" label="运费成本" align="center" />
<el-table-column prop="refund_amount" label="退款金额" align="center" />
<el-table-column prop="red_refund_amount" label="红包退款有责金额" align="center" />
<el-table-column prop="total_profit" label="总利润" align="center" />
<el-table-column label="数据" align="center" width="180">
<template #default="scope">
<el-button type="primary" circle @click="handleAnalysis(scope.row.sku_code)" title="店铺数据"><el-icon><Shop /></el-icon></el-button>
<el-button type="primary" circle :loading="scope.row.loading" @click="trendCharts(scope.row)" title="趋势图"><el-icon><TrendCharts /></el-icon></el-button>
<el-button type="primary" circle @click="orderCharts(scope.row)" title="订单商品销量趋势"><el-icon><List /></el-icon></el-button>
</template>
</el-table-column>
</el-table>
<div class="page-pagination">
<el-pagination
:current-page="page"
background
layout="prev, pager, next, sizes, total"
:total="total"
:page-sizes="[10, 50, 100]"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</div>
</el-card>
<el-dialog v-model="showDialog" width="1000px" title="店铺数据">
<div>
<div style="margin-bottom: 15px;">
<span>时间:</span>
<el-date-picker
v-model="pickTime"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
style="width: 250px;"
:clearable="false"
@change="getDialogList()">
</el-date-picker>
</div>
<div class="tabBox">
<el-table :data="dialogList" style="width: 100%" border v-loading="opa_loading">
<el-table-column label="排名" width="70" align="center">
<template #default="scope">
<span>{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column prop="mx_shop_name" label="店铺" align="center" />
<el-table-column prop="date" label="日期" align="center" />
<el-table-column prop="number" label="销量" align="center" />
<el-table-column prop="order_num" label="单数" align="center" />
<el-table-column prop="avg_number" label="日销量" align="center" />
<el-table-column prop="total_price" label="支付金额" align="center" />
<el-table-column prop="refund_amount" label="退款金额" align="center" />
<el-table-column prop="total_profit" label="利润" align="center" />
<el-table-column label="趋势图" align="center" width="80">
<template #default="scope">
<el-button type="primary" circle @click="getDataLine(scope.row)" :loading="scope.row.loading"><el-icon><TrendCharts /></el-icon></el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-dialog>
<el-dialog v-model="showChart" width="900px" title="趋势图分析">
<el-form label-width="110px">
<el-form-item label="时间:">
<div>
<el-date-picker
v-model="chartTime"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
style="width: 250px;"
:clearable="false"
@change="changeTime0()">
</el-date-picker>
</div>
</el-form-item>
</el-form>
<div id="lineChart" style="width: 100%;height:500px;" v-loading="loading1"></div>
</el-dialog>
<el-dialog v-model="showTrend" width="900px" title="趋势图分析">
<el-form label-width="110px" :inline="true">
<el-form-item label="时间:">
<div>
<el-date-picker
v-model="trendTime"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
style="width: 250px;"
:clearable="false"
@change="changeTime()">
</el-date-picker>
</div>
</el-form-item>
<el-form-item label="总计:"><span style="color: #f00;">{{ sumDaily }}</span></el-form-item>
<el-form-item label="平均值:"><span style="color: #f00;">{{ avg }}</span></el-form-item>
</el-form>
<div id="trendChart" style="width: 100%;height:500px;" v-loading="loading1"></div>
</el-dialog>
<el-dialog v-model="showOrderTrend" width="900px" title="订单商品销量趋势">
<el-form label-width="80px" :inline="true">
<el-form-item label="店铺:">
<el-select v-model="from_shop_ids" @change="changeOrderTime()" placeholder="请选择" clearable filterable multiple collapse-tags style="width: 200px;">
<el-option v-for="it in shopsList" :key="it.id" :label="it.name" :value="it.id" />
</el-select>
</el-form-item>
<el-form-item label="时间:">
<div>
<el-date-picker
v-model="orderTrendTime"
type="datetimerange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 340px;"
:clearable="false"
@change="changeOrderTime()">
</el-date-picker>
</div>
</el-form-item>
<el-form-item label="粒度:">
<el-radio-group v-model="grain_size" size="mini" @change="changeOrderTime()">
<el-radio-button label="60分钟" :value="60" />
<el-radio-button label="30分钟" :value="30" />
<el-radio-button label="15分钟" :value="15" />
</el-radio-group>
</el-form-item>
</el-form>
<div id="orderChart" style="width: 100%;height:500px;" v-loading="loading1"></div>
</el-dialog>
</div>
</template>
<script>
import { onMounted, reactive, toRefs } from "vue"
import { get } from "@/api/request"
import { Search, Shop, TrendCharts, QuestionFilled, List } from '@element-plus/icons'
import * as echarts from 'echarts'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs'
export default {
components: {
Search, Shop, TrendCharts, QuestionFilled, List
},
setup() {
const data = reactive({
filter: {
title: '',
gift: 0
},
rangeTime: [],
statisticsList: [],
page: 1,
pageSize: 10,
total: 0,
loading: false,
opaType: '',
showDialog: false,
opa_loading: false,
itemId: 0,
from_shop_id: 0,
dialogList: [],
pickTime: [],
loading1: false,
dataLineX: [],
dataLineY: [],
showChart: false,
brandList: [],
ascOrDesc: 'desc',
sort: 'number',
showTrend: false,
sumDaily: 0,
avg: 0,
trendTime: [],
chartTime: [],
showOrderTrend: false,
orderTrendTime: [],
from_shop_ids: [],
shopsList: [],
grain_size: 60,
dataLineY2: [],
exportLoading: false
})
function handleSearch() {
data.page = 1
fetchData()
}
function getShopsList() {
get(`/api/mxShops`).then((res) => {
data.shopsList = res.data
})
}
const fetchData = () => {
data.loading = true
let params = {
...data.filter,
page: data.page,
pageSize: data.pageSize,
start_date: data.rangeTime ? data.rangeTime[0] : '',
end_date: data.rangeTime ? data.rangeTime[1] : '',
direction: data.ascOrDesc,
order_by_column: data.sort
}
params.brand_id = data.filter.brand_id || 0
get(`/api/orderItemDailyReport`, params).then((res) => {
data.statisticsList = res.data
data.total = res.meta.total
data.loading = false
}).catch(() => {
data.loading = false
})
}
function handleCurrentChange(e) {
data.page = e
fetchData()
}
function handleSizeChange(e) {
data.page = 1
data.pageSize = e
fetchData()
}
function handleAnalysis(sku_code) {
let end = dayjs().format('YYYY-MM-DD')
let start = dayjs().subtract(30, 'day').format('YYYY-MM-DD')
data.pickTime = [end, end]
data.itemId = sku_code
getDialogList()
data.showDialog = true
}
const getDialogList = () => {
data.opa_loading = true
let params = {
sku_code: data.itemId,
start_date: data.pickTime ? data.pickTime[0] : '',
end_date: data.pickTime ? data.pickTime[1] : ''
}
get(`/api/mxShopDailyReport`, params).then((res) => {
data.dialogList = res.data
data.opa_loading = false
}).catch(() => {
data.opa_loading = false
})
}
const getDataLine = (row) => {
row.loading = true
data.loading1 = true
let end = dayjs().format('YYYY-MM-DD')
let start = dayjs().subtract(30, 'day').format('YYYY-MM-DD')
data.chartTime = [start, end]
data.from_shop_id = row.from_shop_id
let params = {
from_shop_id: row.from_shop_id,
sku_code: data.itemId,
start_date: data.chartTime ? data.chartTime[0] : '',
end_date: data.chartTime ? data.chartTime[1] : ''
}
get(`/api/mxShopTendency`, params).then((res) => {
data.dataLineX = []
data.dataLineY = []
res.data.forEach((item) => {
data.dataLineX.push(item.date)
data.dataLineY.push(item.number)
})
data.showChart = true
setTimeout(() => {
getTrendChart('lineChart')
data.loading1 = false
}, 500)
row.loading = false
}).catch(() => {
row.loading = false
})
}
function changeTime0() {
data.loading1 = true
let params = {
from_shop_id: data.from_shop_id,
sku_code: data.itemId,
start_date: data.chartTime ? data.chartTime[0] : '',
end_date: data.chartTime ? data.chartTime[1] : ''
}
get(`/api/mxShopTendency`, params).then((res) => {
data.dataLineX = []
data.dataLineY = []
res.data.forEach((item) => {
data.dataLineX.push(item.date)
data.dataLineY.push(item.number)
})
setTimeout(() => {
getTrendChart('lineChart')
data.loading1 = false
}, 500)
}).catch(() => {
data.loading = false
})
}
function getTrendChart(id, val = 0) {
let myChart = echarts.init(document.getElementById(id))
let option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['销量']
},
xAxis: {
type: 'category',
data: data.dataLineX
},
yAxis: {},
series: [
{
name: '销量',
type: 'line',
smooth: true,
data: data.dataLineY
}
]
}
if(val == 2) {
option.legend.data.push('累计销量')
option.series.push({
name: '累计销量',
type: 'line',
smooth: true,
data: data.dataLineY2
})
}
myChart.setOption(option)
}
function trendCharts(row) {
data.itemId = row.sku_code
row.loading = true
data.loading1 = true
let end = dayjs().format('YYYY-MM-DD')
let start = dayjs().subtract(30, 'day').format('YYYY-MM-DD')
data.trendTime = [start, end]
let params = {
sku_code: row.sku_code,
start_date: data.trendTime ? data.trendTime[0] : '',
end_date: data.trendTime ? data.trendTime[1] : ''
}
get(`/api/orderItemDailyTendency`, params).then((res) => {
data.dataLineX = []
data.dataLineY = []
res.data.forEach((item) => {
data.dataLineX.push(item.date)
data.dataLineY.push(item.number)
})
data.avg = res.avg
data.sumDaily = res.sum
data.showTrend = true
setTimeout(() => {
getTrendChart('trendChart')
data.loading1 = false
}, 500)
row.loading = false
}).catch(() => {
row.loading = false
})
}
function changeTime() {
data.loading1 = true
let params = {
sku_code: data.itemId,
start_date: data.trendTime ? data.trendTime[0] : '',
end_date: data.trendTime ? data.trendTime[1] : ''
}
get(`/api/orderItemDailyTendency`, params).then((res) => {
data.dataLineX = []
data.dataLineY = []
res.data.forEach((item) => {
data.dataLineX.push(item.date)
data.dataLineY.push(item.number)
})
data.sumDaily = res.sum
data.avg = res.avg
setTimeout(() => {
getTrendChart('trendChart')
data.loading1 = false
}, 500)
data.loading1 = false
}).catch(() => {
data.loading1 = false
})
}
function getBrandList() {
get(`/api/all/brands`).then((res) => {
data.brandList = res.data
})
}
function sortChange({ prop, order }) {
console.log(prop, order)
let arr = prop.split('.')
let length = arr.length
data.ascOrDesc = order == 'ascending' ? 'asc' : 'desc'
data.sort = arr[length - 1]
fetchData()
}
function orderCharts(row) {
data.from_shop_ids = []
data.itemId = row.sku_code
data.grain_size = 60
let end = dayjs().format('YYYY-MM-DD HH:mm:ss')
let start = dayjs().format('YYYY-MM-DD') + ' 00:00:00'
data.orderTrendTime = [start, end]
data.showOrderTrend = true
getOrderCharts()
}
function changeOrderTime() {
getOrderCharts()
}
function getOrderCharts() {
data.loading1 = true
let params = {
sku_code: data.itemId,
from_shop_ids: data.from_shop_ids,
start_time: data.orderTrendTime ? data.orderTrendTime[0] : '',
end_time: data.orderTrendTime ? data.orderTrendTime[1] : '',
minute: data.grain_size
}
get(`/api/orderItemTendency`, params).then((res) => {
data.dataLineX = []
data.dataLineY = []
data.dataLineY2 = []
res.data.forEach((item) => {
data.dataLineX.push(item.time_period)
data.dataLineY.push(item.total_number)
data.dataLineY2.push(item.total)
})
setTimeout(() => {
getTrendChart('orderChart', 2)
data.loading1 = false
}, 500)
data.loading1 = false
}).catch(() => {
data.loading1 = false
})
}
const handleExport = () => {
data.exportLoading = true
let params = {
...data.filter,
page: data.page,
pageSize: data.pageSize,
start_date: data.rangeTime ? data.rangeTime[0] : '',
end_date: data.rangeTime ? data.rangeTime[1] : '',
direction: data.ascOrDesc,
order_by_column: data.sort,
export: 1
}
params.brand_id = data.filter.brand_id || 0
get(`/api/orderItemDailyReport`, params, 'blob').then((res) => {
downLoadXls(res)
ElMessage({ type: "success", message: "导出成功!" })
data.exportLoading = false
}).catch(() => {
data.exportLoading = false
})
}
function downLoadXls(response) {
const content = response
const blob = new Blob([content])
const fileName = `数据统计.xlsx`
if ('download' in document.createElement('a')) {
// 非IE下载
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
} else {
// IE10+下载
navigator.msSaveBlob(blob, fileName)
}
}
onMounted(() => {
fetchData()
getBrandList()
getShopsList()
})
return {
...toRefs(data),
handleSearch,
handleCurrentChange,
handleSizeChange,
fetchData,
handleAnalysis,
getDataLine,
getBrandList,
getDialogList,
sortChange,
trendCharts,
changeTime,
changeTime0,
orderCharts,
getOrderCharts,
changeOrderTime,
getShopsList,
handleExport
}
}
}
</script>
<style lang="scss" scoped>
.searchBox{
display: flex;
flex-wrap: wrap;
background-color: #fff;
padding: 15px 0 0 0;
border-radius: 4px;
margin-bottom: 15px;
.row{
display: flex;
align-items: center;
width: auto;
box-sizing: border-box;
margin-bottom: 15px;
margin-right: 15px;
&.row1{
width: 300px;
}
.span{
display: block;
width: 80px;
font-size: 14px;
text-align: right;
box-sizing: border-box;
}
.right{
width: calc(100% - 80px);
}
}
}
.opaBox{
margin-bottom: 15px;
}
.imgBox{
.el-image{
width: 60px;
height: 60px;
border-radius: 4px;
margin-right: 10px;
display: inline-block;
}
}
.skuBox{
border: 1px solid #e5e5e5;
border-radius: 5px;
padding: 15px 0;
margin-bottom: 15px;
background-color: #f3f3f3;
.tit{
padding-left: 40px;
font-weight: 600;
font-size: 15px;
margin-bottom: 15px;
}
}
</style>