|
|
@@ -0,0 +1,560 @@
|
|
|
+<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
|
|
|
+ yearBeginningAmount: number
|
|
|
+ mayInventoryAmount: number
|
|
|
+ increaseAmount: number
|
|
|
+ yearBeginningBacklog: number
|
|
|
+ mayBacklogAmount: number
|
|
|
+ backlogRatio: string
|
|
|
+}
|
|
|
+
|
|
|
+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: 'SCP项目',
|
|
|
+ yearBeginningAmount: 119.08,
|
|
|
+ mayInventoryAmount: 123.63,
|
|
|
+ increaseAmount: 4.55,
|
|
|
+ yearBeginningBacklog: 23.63,
|
|
|
+ mayBacklogAmount: 43.07,
|
|
|
+ backlogRatio: '34.84%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '紫金山项目',
|
|
|
+ yearBeginningAmount: 79.97,
|
|
|
+ mayInventoryAmount: 82.95,
|
|
|
+ increaseAmount: 2.98,
|
|
|
+ yearBeginningBacklog: 4.32,
|
|
|
+ mayBacklogAmount: 24.76,
|
|
|
+ backlogRatio: '29.85%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '青海东台项目',
|
|
|
+ yearBeginningAmount: 10.42,
|
|
|
+ mayInventoryAmount: 10.32,
|
|
|
+ increaseAmount: -0.1,
|
|
|
+ yearBeginningBacklog: 10.42,
|
|
|
+ mayBacklogAmount: 10.32,
|
|
|
+ backlogRatio: '100.00%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '新疆项目',
|
|
|
+ yearBeginningAmount: 156.11,
|
|
|
+ mayInventoryAmount: 159.45,
|
|
|
+ increaseAmount: 3.34,
|
|
|
+ yearBeginningBacklog: 28,
|
|
|
+ mayBacklogAmount: 26.84,
|
|
|
+ backlogRatio: '16.83%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '泰安项目',
|
|
|
+ yearBeginningAmount: 13.93,
|
|
|
+ mayInventoryAmount: 13.62,
|
|
|
+ increaseAmount: -0.31,
|
|
|
+ yearBeginningBacklog: 5.18,
|
|
|
+ mayBacklogAmount: 6.31,
|
|
|
+ backlogRatio: '46.35%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '伊拉克项目',
|
|
|
+ yearBeginningAmount: 1205.77,
|
|
|
+ mayInventoryAmount: 1098.32,
|
|
|
+ increaseAmount: -107.45,
|
|
|
+ yearBeginningBacklog: 227.67,
|
|
|
+ mayBacklogAmount: 350.58,
|
|
|
+ backlogRatio: '31.92%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '伊拉克一体化项目',
|
|
|
+ yearBeginningAmount: 333.36,
|
|
|
+ mayInventoryAmount: 369.97,
|
|
|
+ increaseAmount: 36.61,
|
|
|
+ yearBeginningBacklog: 0,
|
|
|
+ mayBacklogAmount: 0,
|
|
|
+ backlogRatio: '0.00%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project: '国外项目暂存物资',
|
|
|
+ yearBeginningAmount: 134.49,
|
|
|
+ mayInventoryAmount: 156.73,
|
|
|
+ increaseAmount: 22.24,
|
|
|
+ yearBeginningBacklog: 0,
|
|
|
+ mayBacklogAmount: 0,
|
|
|
+ backlogRatio: '0.00%'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+function formatProjectName(value: string) {
|
|
|
+ return value.replace(/项目$/, '')
|
|
|
+}
|
|
|
+
|
|
|
+function formatAmount(value: number) {
|
|
|
+ return Number(value || 0).toFixed(2)
|
|
|
+}
|
|
|
+
|
|
|
+function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
|
|
|
+ const inventoryTotal = data.reduce((total, item) => total + item.mayInventoryAmount, 0)
|
|
|
+ const backlogTotal = data.reduce((total, item) => total + item.mayBacklogAmount, 0)
|
|
|
+ const inventoryPieData = data.map((item) => ({
|
|
|
+ name: formatProjectName(item.project),
|
|
|
+ value: item.mayInventoryAmount
|
|
|
+ }))
|
|
|
+ const backlogPieData = data
|
|
|
+ .filter((item) => item.mayBacklogAmount > 0)
|
|
|
+ .map((item) => ({
|
|
|
+ name: formatProjectName(item.project),
|
|
|
+ value: item.mayBacklogAmount
|
|
|
+ }))
|
|
|
+ const pieLabel = {
|
|
|
+ show: false,
|
|
|
+ position: 'center' as any,
|
|
|
+ formatter(params: any) {
|
|
|
+ return `{name|${params.name}}\n{value|${formatAmount(params.value)} 万元}`
|
|
|
+ },
|
|
|
+ rich: {
|
|
|
+ name: {
|
|
|
+ color: THEME.text.regular,
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: 500,
|
|
|
+ lineHeight: 22,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ },
|
|
|
+ value: {
|
|
|
+ color: THEME.text.strong,
|
|
|
+ fontSize: 20,
|
|
|
+ fontWeight: 700,
|
|
|
+ lineHeight: 30,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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: 18,
|
|
|
+ textAlign: 'center',
|
|
|
+ textStyle: {
|
|
|
+ color: THEME.text.strong,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 700,
|
|
|
+ lineHeight: 16,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: `5月底积压库存金额\n${formatAmount(backlogTotal)} 万元`,
|
|
|
+ left: '72.5%',
|
|
|
+ top: 18,
|
|
|
+ textAlign: 'center',
|
|
|
+ textStyle: {
|
|
|
+ color: THEME.text.strong,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 700,
|
|
|
+ lineHeight: 16,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ legend: createLegend({
|
|
|
+ type: 'scroll',
|
|
|
+ bottom: 10,
|
|
|
+ left: 10,
|
|
|
+ right: 10,
|
|
|
+ itemWidth: 13,
|
|
|
+ itemHeight: 13,
|
|
|
+ itemGap: 14,
|
|
|
+ textStyle: {
|
|
|
+ color: THEME.text.regular,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 600,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '5月底库存金额',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['48%', '68%'],
|
|
|
+ center: ['27%', '57%'],
|
|
|
+ minAngle: 5,
|
|
|
+ avoidLabelOverlap: true,
|
|
|
+ label: pieLabel,
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 14,
|
|
|
+ shadowColor: THEME.color.blue.shadow
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: inventoryPieData
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '5月底积压库存金额',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['48%', '68%'],
|
|
|
+ center: ['73%', '57%'],
|
|
|
+ minAngle: 5,
|
|
|
+ avoidLabelOverlap: true,
|
|
|
+ label: pieLabel,
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 14,
|
|
|
+ shadowColor: THEME.color.blue.shadow
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: backlogPieData
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
|
|
|
+ const projects = data.map((item) => formatProjectName(item.project))
|
|
|
+ const maxBacklog = Math.max(
|
|
|
+ ...data.map((item) => Math.max(item.yearBeginningBacklog, item.mayBacklogAmount)),
|
|
|
+ 1
|
|
|
+ )
|
|
|
+ const backlogAxisMax = Math.ceil((maxBacklog * 1.15) / 50) * 50
|
|
|
+ const barLabel = {
|
|
|
+ show: true,
|
|
|
+ position: 'right' as any,
|
|
|
+ distance: 7,
|
|
|
+ color: THEME.text.strong,
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 700,
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
+ formatter(params: any) {
|
|
|
+ const value = Number(params.value)
|
|
|
+
|
|
|
+ return formatAmount(value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...ANIMATION,
|
|
|
+ grid: {
|
|
|
+ ...THEME.grid,
|
|
|
+ top: 32
|
|
|
+ },
|
|
|
+ color: [THEME.color.blue.line, THEME.color.orange.line],
|
|
|
+ legend: createLegend(
|
|
|
+ {
|
|
|
+ top: 4,
|
|
|
+ right: 6,
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ itemGap: 16,
|
|
|
+ textStyle: {
|
|
|
+ color: THEME.text.regular,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: 600,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ['年初积压库存', '5月底积压库存']
|
|
|
+ ),
|
|
|
+ tooltip: createTooltip({
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow',
|
|
|
+ shadowStyle: {
|
|
|
+ color: THEME.split
|
|
|
+ }
|
|
|
+ },
|
|
|
+ valueFormatter(value: number) {
|
|
|
+ return `${formatAmount(value)}万元`
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ xAxis: {
|
|
|
+ type: 'value',
|
|
|
+ max: backlogAxisMax,
|
|
|
+ splitNumber: 4,
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: THEME.text.regular,
|
|
|
+ fontSize: 12,
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
+ formatter(value: number) {
|
|
|
+ return `${value}`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: THEME.split,
|
|
|
+ type: 'dashed'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: projects,
|
|
|
+ inverse: true,
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: THEME.text.regular,
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 600,
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
+ margin: 12,
|
|
|
+ width: 132,
|
|
|
+ overflow: 'break',
|
|
|
+ align: 'right'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '年初积压库存',
|
|
|
+ type: 'bar',
|
|
|
+ data: data.map((item) => item.yearBeginningBacklog),
|
|
|
+ barWidth: 12,
|
|
|
+ barGap: '5%',
|
|
|
+ barCategoryGap: '36%',
|
|
|
+ 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, 0, 1, 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.mayBacklogAmount),
|
|
|
+ barWidth: 12,
|
|
|
+ barMinHeight: 0,
|
|
|
+ barGap: '5%',
|
|
|
+ barCategoryGap: '36%',
|
|
|
+ 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, 0, 1, 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()
|
|
|
+}
|
|
|
+
|
|
|
+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('rykb:resize', resizeCharts)
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ window.removeEventListener('resize', resizeCharts)
|
|
|
+ window.removeEventListener('rykb: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>
|