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() } } } }