| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- <script lang="ts" setup>
- import * as echarts from 'echarts'
- import {
- ANIMATION,
- CHART_RENDERER,
- createLegend,
- createTooltip,
- FONT_FAMILY,
- THEME
- } from '@/utils/kb'
- type ActivePanel = 'distribution' | 'trend'
- type InventoryItem = {
- project: string
- mayInventoryAmount: number
- yearBeginningBacklog: number
- mayBacklogAmount: number
- }
- const activePanel = ref<ActivePanel>('distribution')
- const distributionChartRef = ref<HTMLDivElement>()
- const trendChartRef = ref<HTMLDivElement>()
- let distributionChart: echarts.ECharts | null = null
- let trendChart: echarts.ECharts | null = null
- const panelOptions: Array<{ label: string; value: ActivePanel }> = [
- {
- label: '分布',
- value: 'distribution'
- },
- {
- label: '趋势',
- value: 'trend'
- }
- ]
- const activeTitle = computed(() => (activePanel.value === 'distribution' ? '存货分布' : '积压趋势'))
- const inventoryData: InventoryItem[] = [
- {
- project: '东营中心仓',
- mayInventoryAmount: 7.35,
- yearBeginningBacklog: 10.13,
- mayBacklogAmount: 7.02
- },
- {
- project: '吉尔吉斯',
- mayInventoryAmount: 23.82,
- yearBeginningBacklog: 23.82,
- mayBacklogAmount: 25.18
- },
- {
- project: '新疆-连油',
- mayInventoryAmount: 21.9,
- yearBeginningBacklog: 177.02,
- mayBacklogAmount: 19.55
- },
- {
- project: '青海',
- mayInventoryAmount: 52.03,
- yearBeginningBacklog: 177.97,
- mayBacklogAmount: 53.46
- },
- {
- project: '东部-陕蒙',
- mayInventoryAmount: 5.63,
- yearBeginningBacklog: 84.75,
- mayBacklogAmount: 7.65
- },
- {
- project: '东部-胜利',
- mayInventoryAmount: 8.82,
- yearBeginningBacklog: 59.9,
- mayBacklogAmount: 10.67
- },
- {
- project: '西南压裂',
- mayInventoryAmount: 41.78,
- yearBeginningBacklog: 174.18,
- mayBacklogAmount: 27.72
- },
- {
- project: '西南连油',
- mayInventoryAmount: 24.13,
- yearBeginningBacklog: 90.01,
- mayBacklogAmount: 23.37
- },
- {
- project: '伊拉克(国内)',
- mayInventoryAmount: 0.1,
- yearBeginningBacklog: 190.71,
- mayBacklogAmount: 0.1
- },
- {
- project: '大庆工具',
- mayInventoryAmount: 0.12,
- yearBeginningBacklog: 1.79,
- mayBacklogAmount: 0.05
- },
- {
- project: '利比亚(国内)',
- mayInventoryAmount: 0,
- yearBeginningBacklog: 31.33,
- mayBacklogAmount: 0
- }
- ]
- function formatProjectName(value: string) {
- return value.replace(/项目$/, '')
- }
- function formatAmount(value: number) {
- return Number(value || 0).toFixed(2)
- }
- function getInventoryChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
- const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
- const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
- return {
- compact,
- trendGridTop: compact ? 34 : 40,
- trendGridLeft: compact ? 36 : 44,
- trendGridRight: compact ? 8 : 16,
- trendGridBottom: compact ? 12 : 12,
- legendTop: compact ? 0 : 4,
- legendRight: compact ? 2 : 6,
- legendItemSize: compact ? 8 : 10,
- legendGap: compact ? 8 : 12,
- legendFontSize: compact ? 10 : 12,
- axisFontSize: compact ? 10 : 10,
- yAxisLabelMargin: compact ? 6 : 8,
- xAxisLabelRotate: compact ? 45 : 36,
- distributionTitleTop: compact ? 6 : 4,
- distributionTitleFontSize: compact ? 12 : 14,
- distributionTitleLineHeight: compact ? 14 : 16,
- distributionPieRadius: compact ? ['36%', '54%'] : ['48%', '68%'],
- distributionPieCenterY: compact ? '62%' : '57%',
- distributionLegendBottom: compact ? 0 : 10,
- distributionLegendItemSize: compact ? 9 : 13,
- distributionLegendGap: compact ? 8 : 14,
- distributionLegendFontSize: compact ? 10 : 14,
- pieLabelNameFontSize: compact ? 11 : 13,
- pieLabelNameLineHeight: compact ? 18 : 22,
- pieLabelValueFontSize: compact ? 16 : 20,
- pieLabelValueLineHeight: compact ? 22 : 30,
- barWidth: compact ? 10 : 12,
- barGap: compact ? '10%' : '8%',
- barCategoryGap: compact ? '44%' : '38%',
- labelDistance: compact ? 4 : 7,
- labelFontSize: compact ? 10 : 12
- }
- }
- function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
- const layout = getInventoryChartLayout(distributionChartRef)
- const inventoryTotal = data.reduce((total, item) => total + item.mayInventoryAmount, 0)
- const backlogTotal = data.reduce((total, item) => total + item.yearBeginningBacklog, 0)
- return {
- ...ANIMATION,
- color: [
- THEME.color.blue.line,
- THEME.color.orange.line,
- THEME.color.green.line,
- THEME.color.red.line,
- THEME.color.blue.mid,
- THEME.color.orange.mid,
- THEME.color.green.mid,
- THEME.color.red.mid
- ],
- tooltip: createTooltip({
- trigger: 'item',
- formatter(params: any) {
- return `${params.seriesName}<br/>${params.marker}${params.name}:${formatAmount(
- params.value
- )}万元<br/>占比:${params.percent}%`
- }
- }),
- title: [
- {
- text: `5月总积压库存金额\n${formatAmount(inventoryTotal)} 万元`,
- left: '26.5%',
- top: layout.distributionTitleTop,
- textAlign: 'center',
- textStyle: {
- color: THEME.text.strong,
- fontSize: layout.distributionTitleFontSize,
- fontWeight: 700,
- lineHeight: layout.distributionTitleLineHeight,
- fontFamily: FONT_FAMILY
- }
- },
- {
- text: `总库存金额\n${formatAmount(backlogTotal)} 万元`,
- left: '71.5%',
- top: layout.distributionTitleTop,
- textAlign: 'center',
- textStyle: {
- color: THEME.text.strong,
- fontSize: layout.distributionTitleFontSize,
- fontWeight: 700,
- lineHeight: layout.distributionTitleLineHeight,
- fontFamily: FONT_FAMILY
- }
- }
- ],
- legend: createLegend({
- type: 'scroll',
- bottom: layout.distributionLegendBottom,
- left: 10,
- right: 10,
- itemWidth: layout.distributionLegendItemSize,
- itemHeight: layout.distributionLegendItemSize,
- itemGap: layout.distributionLegendGap,
- textStyle: {
- color: THEME.text.regular,
- fontSize: layout.distributionLegendFontSize,
- fontWeight: 600,
- fontFamily: FONT_FAMILY
- }
- }),
- series: [
- {
- name: '5月总积压库存金额',
- type: 'pie',
- radius: layout.distributionPieRadius,
- center: ['27%', layout.distributionPieCenterY],
- minAngle: 5,
- label: { show: false },
- data: data.map((item) => ({
- name: item.project,
- value: item.mayInventoryAmount
- }))
- },
- {
- name: '总库存金额',
- type: 'pie',
- radius: layout.distributionPieRadius,
- center: ['73%', layout.distributionPieCenterY],
- minAngle: 5,
- label: { show: false },
- data: data
- .filter((item) => item.yearBeginningBacklog > 0)
- .map((item) => ({
- name: item.project,
- value: item.yearBeginningBacklog
- }))
- }
- ]
- }
- }
- function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
- const projects = data.map((item) => formatProjectName(item.project))
- const maxBacklog = Math.max(
- ...data.map((item) => Math.max(item.mayInventoryAmount, item.mayBacklogAmount)),
- 1
- )
- const backlogAxisMax = Math.ceil((maxBacklog * 1.15) / 50) * 50
- const layout = getInventoryChartLayout(trendChartRef)
- const barLabel = {
- show: false,
- position: 'top' as any,
- distance: layout.labelDistance,
- color: THEME.text.strong,
- fontSize: layout.labelFontSize,
- fontWeight: 700,
- fontFamily: FONT_FAMILY,
- formatter(params: any) {
- const value = Number(params.value)
- return formatAmount(value)
- }
- }
- return {
- ...ANIMATION,
- grid: {
- ...THEME.grid,
- top: layout.trendGridTop,
- left: layout.trendGridLeft,
- right: layout.trendGridRight,
- bottom: layout.trendGridBottom
- },
- color: [THEME.color.blue.line, THEME.color.orange.line],
- legend: createLegend(
- {
- top: layout.legendTop,
- right: layout.legendRight,
- itemWidth: layout.legendItemSize,
- itemHeight: layout.legendItemSize,
- itemGap: layout.legendGap,
- textStyle: {
- color: THEME.text.regular,
- fontSize: layout.legendFontSize,
- fontWeight: 600,
- fontFamily: FONT_FAMILY
- }
- },
- ['2026期初', '5月总积压']
- ),
- tooltip: createTooltip({
- trigger: 'axis',
- axisPointer: {
- type: 'shadow',
- shadowStyle: {
- color: THEME.split
- }
- },
- valueFormatter(value: number) {
- return `${formatAmount(value)}万元`
- }
- }),
- xAxis: {
- type: 'category',
- data: projects,
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: THEME.text.regular,
- fontSize: layout.axisFontSize,
- fontWeight: 600,
- fontFamily: FONT_FAMILY,
- interval: 0,
- margin: layout.yAxisLabelMargin,
- rotate: layout.xAxisLabelRotate
- }
- },
- yAxis: {
- type: 'value',
- max: backlogAxisMax,
- splitNumber: 4,
- axisLine: {
- show: false
- },
- axisTick: {
- show: false
- },
- axisLabel: {
- color: THEME.text.regular,
- fontSize: layout.axisFontSize,
- fontFamily: FONT_FAMILY,
- formatter(value: number) {
- return `${value}`
- }
- },
- splitLine: {
- lineStyle: {
- color: THEME.split,
- type: 'dashed'
- }
- }
- },
- series: [
- {
- name: '2026期初',
- type: 'bar',
- data: data.map((item) => item.mayBacklogAmount),
- barWidth: layout.barWidth,
- barGap: layout.barGap,
- barCategoryGap: layout.barCategoryGap,
- barMinHeight: 0,
- showBackground: false,
- backgroundStyle: {
- color: THEME.split,
- borderRadius: 999
- },
- label: barLabel,
- labelLayout: {
- hideOverlap: true
- },
- itemStyle: {
- shadowBlur: 10,
- shadowColor: THEME.color.blue.bg,
- color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
- { offset: 0, color: THEME.color.blue.light },
- { offset: 0.55, color: THEME.color.blue.mid },
- { offset: 1, color: THEME.color.blue.line }
- ])
- }
- },
- {
- name: '5月总积压',
- type: 'bar',
- data: data.map((item) => item.mayInventoryAmount),
- barWidth: layout.barWidth,
- barMinHeight: 0,
- barGap: layout.barGap,
- barCategoryGap: layout.barCategoryGap,
- showBackground: false,
- backgroundStyle: {
- color: THEME.split,
- borderRadius: 999
- },
- label: barLabel,
- labelLayout: {
- hideOverlap: true
- },
- itemStyle: {
- shadowBlur: 10,
- shadowColor: THEME.color.orange.bg,
- color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
- { offset: 0, color: THEME.color.orange.light },
- { offset: 0.55, color: THEME.color.orange.mid },
- { offset: 1, color: THEME.color.orange.line }
- ])
- }
- }
- ]
- }
- }
- function initChart(
- chartRef: Ref<HTMLDivElement | undefined>,
- chart: echarts.ECharts | null,
- option: echarts.EChartsOption
- ) {
- if (!chartRef.value) return chart
- chart?.dispose()
- const nextChart = echarts.init(chartRef.value, undefined, {
- renderer: CHART_RENDERER
- })
- nextChart.setOption(option, true)
- return nextChart
- }
- function renderDistributionChart() {
- distributionChart?.setOption(getDistributionOption(inventoryData), true)
- }
- function renderTrendChart() {
- trendChart?.setOption(getTrendOption(inventoryData), true)
- }
- function initDistributionChart() {
- distributionChart = initChart(
- distributionChartRef,
- distributionChart,
- getDistributionOption(inventoryData)
- )
- }
- function initTrendChart() {
- trendChart = initChart(trendChartRef, trendChart, getTrendOption(inventoryData))
- }
- function resizeCharts() {
- distributionChart?.resize()
- trendChart?.resize()
- renderDistributionChart()
- renderTrendChart()
- }
- function destroyCharts() {
- distributionChart?.dispose()
- trendChart?.dispose()
- distributionChart = null
- trendChart = null
- }
- watch(activePanel, (value) => {
- nextTick(() => {
- if (value === 'distribution') {
- if (!distributionChart) initDistributionChart()
- renderDistributionChart()
- } else {
- if (!trendChart) initTrendChart()
- renderTrendChart()
- }
- resizeCharts()
- })
- })
- onMounted(() => {
- initDistributionChart()
- window.addEventListener('resize', resizeCharts)
- window.addEventListener('rdkb:resize', resizeCharts)
- })
- onUnmounted(() => {
- window.removeEventListener('resize', resizeCharts)
- window.removeEventListener('rdkb:resize', resizeCharts)
- destroyCharts()
- })
- </script>
- <template>
- <div class="panel flex flex-col">
- <div class="panel-title flex items-center justify-between">
- <div class="kb-panel-title-text flex items-center">
- <div class="icon-decorator">
- <span></span>
- <span></span>
- </div>
- {{ activeTitle }}
- </div>
- <el-segmented
- v-model="activePanel"
- :options="panelOptions"
- size="small"
- class="inventory-switch" />
- </div>
- <div class="flex-1 min-h-0">
- <div
- v-show="activePanel === 'distribution'"
- ref="distributionChartRef"
- class="inventory-chart"></div>
- <div v-show="activePanel === 'trend'" ref="trendChartRef" class="inventory-chart"></div>
- </div>
- </div>
- </template>
- <style lang="scss" scoped>
- @import url('@/styles/kb.scss');
- .inventory-switch {
- --el-segmented-item-selected-color: #03409b;
- --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
- --el-segmented-bg-color: rgb(31 91 184 / 10%);
- --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
- min-height: calc(26px * var(--kb-scale, 1));
- padding: calc(2px * var(--kb-scale, 1));
- border: 1px solid rgb(31 91 184 / 12%);
- transform: translateY(calc(-2px * var(--kb-scale, 1)));
- :deep(.el-segmented__item) {
- min-height: calc(22px * var(--kb-scale, 1));
- padding: 0 calc(8px * var(--kb-scale, 1));
- font-size: calc(13px * var(--kb-scale, 1));
- font-weight: 600;
- color: #29527f;
- }
- }
- .inventory-chart {
- width: 100%;
- height: 100%;
- min-height: 0;
- }
- </style>
|