<template>
|
<div>
|
<PanelHeader title="客户贡献排名" />
|
<div class="panel-item-customers">
|
<div class="switch-container">
|
<DateTypeSwitch v-model="dateType" @change="handleDateTypeChange" />
|
</div>
|
<Echarts
|
ref="chart"
|
:chartStyle="chartStyle"
|
:grid="grid"
|
:legend="{ show: false }"
|
:series="series"
|
:tooltip="tooltip"
|
:xAxis="xAxis"
|
:yAxis="yAxis"
|
:options="{ backgroundColor: 'transparent', textStyle: { color: BI.text } }"
|
style="height: 360px"
|
/>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, computed } from 'vue'
|
import Echarts from '@/components/Echarts/echarts.vue'
|
import PanelHeader from '../PanelHeader.vue'
|
import DateTypeSwitch from '../DateTypeSwitch.vue'
|
import { customerContributionRanking } from '@/api/viewIndex.js'
|
import { BI, withTooltip } from '../../theme.js'
|
|
const chartStyle = {
|
width: '100%',
|
height: '100%',
|
}
|
|
const dateType = ref(3) // 1=周 2=月 3=季度,默认季度
|
|
// 原始数据(统一成 { NAME, NUM })
|
const dataArr = ref([])
|
|
const dataArray = computed(() => {
|
const sortedAsc = [...dataArr.value].sort((a, b) => a.NUM - b.NUM)
|
return sortedAsc.length > 5 ? sortedAsc.slice(-5) : sortedAsc
|
})
|
|
const total = computed(() => dataArray.value.reduce((sum, v) => sum + Number(v.NUM || 0), 0))
|
|
const xdataName = computed(() => dataArray.value.map((v) => v.NAME))
|
|
// 排名配色:Top1 金色,Top2-3 亮蓝,其余暗蓝
|
const rankColor = (rank) => {
|
if (rank === 1) return '#F0B14A'
|
if (rank <= 3) return BI.accentBright
|
return '#2C4A9E'
|
}
|
|
const getRank = (i) => dataArray.value.length - i
|
|
const dataNum = computed(() => {
|
return dataArray.value.map((v, i) => {
|
const rank = getRank(i)
|
return {
|
value: Number(v.NUM),
|
itemStyle: {
|
color: rankColor(rank),
|
barBorderRadius: [0, 10, 10, 0],
|
},
|
}
|
})
|
})
|
|
const bgData = computed(() => {
|
const maxValue = dataNum.value.length ? Math.max(...dataNum.value.map((v) => v.value)) : 0
|
return dataNum.value.map(() => maxValue * 1.08 || 1)
|
})
|
|
const tooltip = withTooltip({
|
trigger: 'axis',
|
formatter: function (params) {
|
let result = params[0].axisValueLabel + '<br/>'
|
result += `<div>${params[0].marker}${Number(params[0].value || 0).toLocaleString('zh-CN')}元</div>`
|
return result
|
},
|
})
|
|
const grid = computed(() => ({ top: 0, left: '20%', right: '10%', bottom: 0 }))
|
|
const xAxis = computed(() => [
|
{
|
splitLine: { show: false },
|
axisLine: { show: false },
|
axisLabel: { show: false },
|
axisTick: { show: false },
|
},
|
])
|
|
const yAxis = computed(() => [
|
{
|
type: 'category',
|
inverse: false,
|
data: xdataName.value,
|
axisLabel: {
|
formatter: (value) => {
|
if (!value) return ''
|
const maxLen = 6 // 每行最多字符数,可按需调整
|
if (value.length <= maxLen) return `{a|${value}}`
|
|
const lines = []
|
for (let i = 0; i < value.length; i += maxLen) {
|
lines.push(value.slice(i, i + maxLen))
|
}
|
return lines.map((line) => `{a|${line}}`).join('\n')
|
},
|
rich: {
|
a: {
|
width: 120,
|
fontSize: 14,
|
color: BI.textStrong,
|
padding: [5, 4, 5, 0],
|
align: 'right',
|
},
|
},
|
},
|
axisLine: { show: false },
|
axisTick: { show: false },
|
splitLine: { show: false },
|
},
|
{
|
type: 'category',
|
data: dataNum.value.map((item) => item.value),
|
axisLabel: {
|
formatter: (params, index) => {
|
const value = typeof params === 'object' ? params.value : params
|
const percent = total.value ? ((value / total.value) * 100).toFixed(0) : 0
|
const rank = getRank(index)
|
return `{a${rank}|${percent} }{b${rank}|%}`
|
},
|
rich: dataNum.value.reduce((rich, _, i) => {
|
const rank = getRank(i)
|
const color = rank === 1 ? '#F0B14A' : rank <= 3 ? BI.accentBright : BI.text
|
rich[`a${rank}`] = { fontSize: 16, color, verticalAlign: 'bottom' }
|
rich[`b${rank}`] = { fontSize: 12, color, verticalAlign: 'bottom' }
|
return rich
|
}, {}),
|
},
|
axisLine: { show: false },
|
axisTick: { show: false },
|
splitLine: { show: false },
|
},
|
])
|
|
// series:背景轨道 + 实色条
|
const series = computed(() => [
|
{
|
name: '背景',
|
z: 5,
|
type: 'bar',
|
barWidth: 18,
|
tooltip: { show: false },
|
itemStyle: {
|
color: 'rgba(143, 163, 200, 0.12)',
|
barBorderRadius: [0, 10, 10, 0],
|
},
|
data: bgData.value,
|
},
|
{
|
name: '金额',
|
z: 6,
|
type: 'bar',
|
barWidth: 18,
|
barGap: '-100%',
|
data: dataNum.value,
|
},
|
])
|
|
const normalizeItem = (item) => {
|
const name =
|
item?.NAME ??
|
item?.name ??
|
item?.customerName ??
|
item?.customer ??
|
item?.label ??
|
'-'
|
|
const num =
|
item?.NUM ??
|
item?.num ??
|
item?.value ??
|
item?.amount ??
|
item?.money ??
|
0
|
|
return { NAME: String(name), NUM: Number(num) || 0 }
|
}
|
|
const getMockListByType = (type) => {
|
// 模拟假数据(金额贡献排名)
|
// type: 1=周 2=月 3=季度
|
if (type === 2) {
|
return [
|
{ NAME: '华东精密', NUM: 5120000 },
|
{ NAME: '星辰电子', NUM: 3860000 },
|
{ NAME: '启航科技', NUM: 2720000 },
|
{ NAME: '铭诚制造', NUM: 2160000 },
|
{ NAME: '远景材料', NUM: 1430000 },
|
{ NAME: '德润贸易', NUM: 910000 },
|
{ NAME: '宏达配套', NUM: 680000 },
|
]
|
}
|
if (type === 3) {
|
return [
|
{ NAME: '华东精密', NUM: 16800000 },
|
{ NAME: '星辰电子', NUM: 12960000 },
|
{ NAME: '启航科技', NUM: 9720000 },
|
{ NAME: '铭诚制造', NUM: 7560000 },
|
{ NAME: '远景材料', NUM: 5430000 },
|
{ NAME: '德润贸易', NUM: 3910000 },
|
{ NAME: '宏达配套', NUM: 2680000 },
|
]
|
}
|
return [
|
{ NAME: '华东精密', NUM: 1280000 },
|
{ NAME: '星辰电子', NUM: 860000 },
|
{ NAME: '启航科技', NUM: 720000 },
|
{ NAME: '铭诚制造', NUM: 560000 },
|
{ NAME: '远景材料', NUM: 430000 },
|
{ NAME: '德润贸易', NUM: 310000 },
|
{ NAME: '宏达配套', NUM: 180000 },
|
]
|
}
|
|
const setMockData = (type) => {
|
dataArr.value = getMockListByType(type).map(normalizeItem)
|
}
|
|
const fetchCustomerRanking = () => {
|
customerContributionRanking({ type: dateType.value })
|
.then((res) => {
|
if (res.code === 200 && Array.isArray(res.data) && res.data.length > 0) {
|
dataArr.value = res.data.map((item) => ({
|
NAME: item.customerName,
|
NUM: Number(item.totalAmount) || 0,
|
}))
|
} else {
|
setMockData(dateType.value)
|
}
|
})
|
.catch((error) => {
|
console.error('获取客户金额贡献排名失败:', error)
|
setMockData(dateType.value)
|
})
|
}
|
|
const handleDateTypeChange = () => {
|
fetchCustomerRanking()
|
}
|
|
onMounted(() => {
|
fetchCustomerRanking()
|
})
|
</script>
|
|
<style scoped>
|
.panel-item-customers {
|
border: 1px solid var(--bi-panel-border);
|
background: var(--bi-panel-bg);
|
border-radius: 8px;
|
padding: 18px;
|
width: 100%;
|
height: 449px;
|
}
|
|
.switch-container {
|
display: flex;
|
justify-content: flex-end;
|
margin-bottom: 16px;
|
}
|
</style>
|