import * as echarts from 'echarts'
import { getShopChannelDiversity, getShopDepartmentEfficiency } from '@/api/order'
const EMPTY_DATA_HINTS = ['未上传', '请先上传', '暂无数据', '无数据', 'not found', 'no data']
function createChartState(defaultTitle) {
return {
loading: true,
title: defaultTitle,
message: '',
isError: false
}
}
export default {
name: 'OrderEfficiency',
data() {
return {
barChartInstance: null,
pieChartInstance: null,
barChart: createChartState('部门效率数据暂不可用'),
pieChart: createChartState('渠道多样性数据暂不可用')
}
},
mounted() {
this.loadCharts()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
this.disposeCharts()
},
methods: {
async loadCharts() {
await Promise.all([this.initBarChart(), this.initPieChart()])
},
disposeCharts() {
if (this.barChartInstance) {
this.barChartInstance.dispose()
this.barChartInstance = null
}
if (this.pieChartInstance) {
this.pieChartInstance.dispose()
this.pieChartInstance = null
}
},
resetChartState(target, title) {
target.loading = true
target.title = title
target.message = ''
target.isError = false
},
getErrorMessage(error, fallback) {
return (
error?.response?.data?.message ||
error?.response?.data?.msg ||
error?.message ||
fallback
)
},
isEmptyDataMessage(message) {
const normalized = String(message || '').toLowerCase()
return EMPTY_DATA_HINTS.some(item => normalized.includes(item.toLowerCase()))
},
setChartStatus(target, title, message, isError = false) {
target.title = title
target.message = message
target.isError = isError
},
normalizeBarData(response) {
if (!response || response.success !== true || !response.data) {
throw new Error((response && response.message) || '部门效率数据加载失败')
}
const rows = Object.entries(response.data)
.map(([name, value]) => ({
name,
value: Number(value)
}))
.filter(item => Number.isFinite(item.value))
if (!rows.length) {
throw new Error('请先上传店铺价值 CSV 文件')
}
return rows
},
normalizePieData(response) {
if (!response || response.success !== true || !response.data) {
throw new Error((response && response.message) || '渠道多样性数据加载失败')
}
const rows = Object.entries(response.data)
.map(([name, value]) => ({
name,
value: Number(value)
}))
.filter(item => Number.isFinite(item.value) && item.value > 0)
if (!rows.length) {
throw new Error('请先上传店铺价值 CSV 文件')
}
return rows
},
initBarChartInstance() {
const chartEl = this.$refs.barChartRef
if (!chartEl) return null
this.barChartInstance = echarts.getInstanceByDom(chartEl) || echarts.init(chartEl)
return this.barChartInstance
},
initPieChartInstance() {
const chartEl = this.$refs.pieChartRef
if (!chartEl) return null
this.pieChartInstance = echarts.getInstanceByDom(chartEl) || echarts.init(chartEl)
return this.pieChartInstance
},
async initBarChart() {
this.resetChartState(this.barChart, '部门效率数据暂不可用')
try {
const response = await getShopDepartmentEfficiency()
const chartData = this.normalizeBarData(response)
await this.$nextTick()
const chart = this.initBarChartInstance()
if (!chart) return
chart.setOption({
title: { text: '部门效率分析', left: 'center' },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: '{b}
平均销售额: {c} 元'
},
grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
xAxis: {
type: 'category',
data: chartData.map(item => item.name),
axisLabel: { rotate: 30, interval: 0 }
},
yAxis: {
type: 'value',
name: '平均销售额(元)'
},
series: [{
name: '平均销售额',
type: 'bar',
data: chartData.map(item => Number(item.value.toFixed(2))),
barWidth: '40%',
itemStyle: {
borderRadius: [5, 5, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#188df0' }
])
}
}]
}, true)
} catch (error) {
const message = this.getErrorMessage(error, '部门效率数据加载失败')
this.setChartStatus(
this.barChart,
this.isEmptyDataMessage(message) ? '暂无部门效率数据' : '部门效率数据加载失败',
this.isEmptyDataMessage(message) ? '请先在店铺价值页面上传 CSV 文件,再查看该图表。' : message,
!this.isEmptyDataMessage(message)
)
} finally {
this.barChart.loading = false
}
},
async initPieChart() {
this.resetChartState(this.pieChart, '渠道多样性数据暂不可用')
try {
const response = await getShopChannelDiversity()
const chartData = this.normalizePieData(response)
await this.$nextTick()
const chart = this.initPieChartInstance()
if (!chart) return
chart.setOption({
title: { text: '渠道商品多样性', left: 'center' },
tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left', top: '10%' },
series: [{
name: '渠道',
type: 'pie',
radius: [20, 140],
center: ['50%', '60%'],
roseType: 'area',
itemStyle: { borderRadius: 5 },
data: chartData
}]
}, true)
} catch (error) {
const message = this.getErrorMessage(error, '渠道多样性数据加载失败')
this.setChartStatus(
this.pieChart,
this.isEmptyDataMessage(message) ? '暂无渠道多样性数据' : '渠道多样性数据加载失败',
this.isEmptyDataMessage(message) ? '请先在店铺价值页面上传 CSV 文件,再查看该图表。' : message,
!this.isEmptyDataMessage(message)
)
} finally {
this.pieChart.loading = false
}
},
handleResize() {
if (this.barChartInstance) {
this.barChartInstance.resize()
}
if (this.pieChartInstance) {
this.pieChartInstance.resize()
}
}
}
}