640 lines
21 KiB
Vue
640 lines
21 KiB
Vue
<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> 筛选</el-button>
|
||
</div>
|
||
</div>
|
||
<el-card shadow="never">
|
||
<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="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>
|
||
<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 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,
|
||
trendTime: [],
|
||
chartTime: [],
|
||
showOrderTrend: false,
|
||
orderTrendTime: [],
|
||
from_shop_ids: [],
|
||
shopsList: [],
|
||
grain_size: 60,
|
||
dataLineY2: []
|
||
})
|
||
|
||
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.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
|
||
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
|
||
})
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchData()
|
||
getBrandList()
|
||
getShopsList()
|
||
})
|
||
|
||
return {
|
||
...toRefs(data),
|
||
handleSearch,
|
||
handleCurrentChange,
|
||
handleSizeChange,
|
||
fetchData,
|
||
handleAnalysis,
|
||
getDataLine,
|
||
getBrandList,
|
||
getDialogList,
|
||
sortChange,
|
||
trendCharts,
|
||
changeTime,
|
||
changeTime0,
|
||
orderCharts,
|
||
getOrderCharts,
|
||
changeOrderTime,
|
||
getShopsList
|
||
}
|
||
}
|
||
}
|
||
</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);
|
||
}
|
||
}
|
||
}
|
||
.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>
|