Просмотр исходного кода

Merge branch 'master' of http://1.94.244.160:3000/shuzhihua/pms-iot-vue

Zimo 1 неделя назад
Родитель
Сommit
43367cce65

+ 15 - 0
src/api/pms/stat/index.ts

@@ -208,6 +208,21 @@ export const IotStatApi = {
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
   },
 
+  // 瑞恒看板-存货分布、积压趋势
+  getInventoryDistribution: async (params: any) => {
+    return await request.get({ url: `/pms/iot-prod-inventory-aging/inventoryStatus`, params })
+  },
+
+  // 积压趋势
+  getInventoryAging: async (params: any) => {
+    return await request.get({ url: `/pms/iot-prod-inventory-aging/inventoryTrend`, params })
+  },
+
+  // 积压明细
+  getInventoryDetail: async (params: any) => {
+    return await request.get({ url: `/pms/iot-prod-inventory-aging/inventoryAgeDetail`, params })
+  },
+
   getMaintainCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/maintain/count/` + params })
   },

+ 0 - 1
src/views/pms/stat/rhkb.vue

@@ -102,7 +102,6 @@ onUnmounted(() => {
               {{ tab.label }}
             </button>
           </div>
-
           <div v-if="activePage === 'home'" class="kb-home-page">
             <rhsummary class="kb-stage-card kb-stage-card--1" />
             <div class="kb-chart-grid">

+ 68 - 0
src/views/pms/stat/rhkb/data.json

@@ -0,0 +1,68 @@
+{
+  "totalInventory": 36392625,
+  "totalOverStock": 13570778,
+  "factoryNameOverStockPair": {
+    "新疆科瑞氮气工厂": 176738,
+    "瑞恒新疆": 9331039,
+    "瑞恒兴域": 4063001
+  },
+  "factoryNameInventoryPair": {
+    "新疆科瑞氮气工厂": 176738,
+    "瑞恒新疆": 20297592,
+    "瑞恒兴域": 15918295
+  },
+  "storageNameOverStockPair": {
+    "6006-3001": 7344717,
+    "6000-7001": 0,
+    "6006-1001": 319134,
+    "6006-3004": 628908,
+    "6006-3002": 662088,
+    "6006-3003": 376192,
+    "6000-null": 99812,
+    "6000-3008": 27986,
+    "6000-1002": 1441498,
+    "6000-1003": 589,
+    "6000-3004": 102024,
+    "6000-1004": 326682,
+    "6000-3007": 178674,
+    "6000-3006": 1692,
+    "6000-1005": 265905,
+    "6000-3001": 50439,
+    "6000-3003": 0,
+    "6000-3002": 10299,
+    "6000-1001": 1557401,
+    "6000-6002": 0,
+    "6000-8002": 0,
+    "6000-8001": 0,
+    "6006-6000": 0,
+    "5010-3009": 93527,
+    "5010-1002": 83211
+  },
+  "storageNameInventoryPair": {
+    "6006-3001": 14121779,
+    "6000-7001": 737776,
+    "6006-1001": 699665,
+    "6006-3004": 2960010,
+    "6006-3002": 1810637,
+    "6006-3003": 394306,
+    "6000-null": 99812,
+    "6000-3008": 46357,
+    "6000-1002": 2297667,
+    "6000-1003": 4398,
+    "6000-3004": 102024,
+    "6000-1004": 1231117,
+    "6000-3007": 516405,
+    "6000-3006": 27200,
+    "6000-1005": 527621,
+    "6000-3001": 251052,
+    "6000-3003": 6605752,
+    "6000-3002": 16121,
+    "6000-1001": 2637684,
+    "6000-6002": 670540,
+    "6000-8002": 45968,
+    "6000-8001": 100801,
+    "6006-6000": 311195,
+    "5010-3009": 93527,
+    "5010-1002": 83211
+  }
+}

+ 596 - 234
src/views/pms/stat/rhkb/inventorySituation.vue

@@ -9,7 +9,8 @@ import {
   THEME
 } from '@/utils/kb'
 
-type ActivePanel = 'distribution' | 'trend'
+import { IotStatApi } from '@/api/pms/stat'
+import dayjs from 'dayjs'
 
 type InventoryItem = {
   project: string
@@ -18,7 +19,42 @@ type InventoryItem = {
   mayBacklogAmount: number
 }
 
+type InventoryDistributionResponse = {
+  totalInventory: number
+  totalOverStock: number
+  factoryNameOverStockPair: Record<string, number>
+  factoryNameInventoryPair: Record<string, number>
+  storageNameOverStockPair?: Record<string, number>
+  storageNameInventoryPair?: Record<string, number>
+}
+
+type InventoryAgingResponse = Record<string, number>
+
+type InventoryAgingPoint = {
+  month: string
+  value: number
+}
+
+type InventoryDetail = {
+  '1年以内': number
+  '3年以上': number
+  年初存货: number
+  年初积压: number
+  '1-2年': number
+  期末积压: number
+  '2-3年': number
+  积压比: number
+  期末库存: number
+}
+
+type ActivePanel = 'distribution' | 'trend' | 'detail'
+
 const activePanel = ref<ActivePanel>('distribution')
+const DEFAULT_DATE = dayjs().subtract(1, 'day').format('YYYY-MM-DD')
+const selectedDate = ref(DEFAULT_DATE)
+const inventoryResult = ref<InventoryDistributionResponse | null>(null)
+const inventoryAgingResult = ref<InventoryAgingResponse>({})
+const inventoryDetailResult = ref<InventoryDetail | null>(null)
 const distributionChartRef = ref<HTMLDivElement>()
 const trendChartRef = ref<HTMLDivElement>()
 
@@ -26,73 +62,66 @@ 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' }
+  { label: '存货分布', value: 'distribution' },
+  { label: '趋势', value: 'trend' },
+  { label: '明细', value: 'detail' }
 ]
 
+const activeTitle = computed(() => {
+  if (activePanel.value === 'trend') return '积压趋势'
+  if (activePanel.value === 'detail') return '积压明细'
+  return '存货分布'
+})
+
 const yuanToWan = (value: number) => Number((value / 10000).toFixed(2))
 
-const inventoryData: InventoryItem[] = [
-  {
-    project: '东营库',
-    mayInventoryAmount: yuanToWan(2921827.56),
-    yearBeginningBacklog: yuanToWan(1644059.52),
-    mayBacklogAmount: yuanToWan(1436126.52)
-  },
-  {
-    project: '川庆',
-    mayInventoryAmount: yuanToWan(491050.94),
-    yearBeginningBacklog: 0,
-    mayBacklogAmount: yuanToWan(308801.56)
-  },
-  {
-    project: '南美',
-    mayInventoryAmount: yuanToWan(2491993.31),
-    yearBeginningBacklog: 0,
-    mayBacklogAmount: 0
-  },
-  {
-    project: '非洲',
-    mayInventoryAmount: yuanToWan(2276836.24),
-    yearBeginningBacklog: yuanToWan(175144.46),
-    mayBacklogAmount: yuanToWan(391697.89)
-  },
-  {
-    project: '中东',
-    mayInventoryAmount: yuanToWan(127300.64),
-    yearBeginningBacklog: yuanToWan(14784.43),
-    mayBacklogAmount: yuanToWan(103461.07)
-  },
-  {
-    project: '中亚',
-    mayInventoryAmount: yuanToWan(188389.42),
-    yearBeginningBacklog: 0,
-    mayBacklogAmount: 0
-  },
-  {
-    project: '塔河库',
-    mayInventoryAmount: yuanToWan(17175342.26),
-    yearBeginningBacklog: yuanToWan(9890389.23),
-    mayBacklogAmount: yuanToWan(9261631.2)
-  },
-  {
-    project: '塔里木',
-    mayInventoryAmount: yuanToWan(4599437.38),
-    yearBeginningBacklog: 0,
-    mayBacklogAmount: yuanToWan(890890.45)
-  },
-  {
-    project: '吐哈库',
-    mayInventoryAmount: yuanToWan(2342089.06),
-    yearBeginningBacklog: yuanToWan(1097925.36),
-    mayBacklogAmount: yuanToWan(1190879.22)
+function formatAmount(value: number) {
+  return Number(value || 0).toFixed(2)
+}
+
+function formatRatio(value: number) {
+  return `${(Number(value || 0) * 100).toFixed(2)}%`
+}
+
+function formatWanValue(value: number) {
+  return formatAmount(yuanToWan(Number(value || 0)))
+}
+
+const detailTableData = computed(() => {
+  const data = inventoryDetailResult.value
+  if (!data) return []
+
+  return [
+    {
+      beginningInventory: formatWanValue(data['年初存货']),
+      beginningOverStock: formatWanValue(data['年初积压']),
+      endingInventory: formatWanValue(data['期末库存']),
+      endingOverStock: formatWanValue(data['期末积压']),
+      withinOneYear: formatWanValue(data['1年以内']),
+      oneToTwoYears: formatWanValue(data['1-2年']),
+      twoToThreeYears: formatWanValue(data['2-3年']),
+      overThreeYears: formatWanValue(data['3年以上']),
+      overStockRatio: formatRatio(data['积压比'])
+    }
+  ]
+})
+
+function getFactoryInventoryData(data: InventoryDistributionResponse | null): InventoryItem[] {
+  if (!data) {
+    return []
   }
-]
 
-const activeTitle = computed(() => (activePanel.value === 'distribution' ? '存货分布' : '积压趋势'))
+  const factoryNames = new Set([
+    ...Object.keys(data.factoryNameInventoryPair || {}),
+    ...Object.keys(data.factoryNameOverStockPair || {})
+  ])
 
-function formatAmount(value: number) {
-  return Number(value || 0).toFixed(2)
+  return [...factoryNames].map((project) => ({
+    project,
+    mayInventoryAmount: yuanToWan(data.factoryNameInventoryPair?.[project] || 0),
+    yearBeginningBacklog: 0,
+    mayBacklogAmount: yuanToWan(data.factoryNameOverStockPair?.[project] || 0)
+  }))
 }
 
 function getChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
@@ -101,14 +130,14 @@ function getChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
 
   return {
     compact,
-    distributionTitleTop: compact ? 6 : 18,
-    distributionTitleFontSize: compact ? 12 : 14,
+    distributionTitleTop: compact ? 3 : 13,
+    distributionTitleFontSize: compact ? 9 : 11,
     distributionTitleLineHeight: compact ? 14 : 16,
-    distributionPieRadius: compact ? ['36%', '54%'] : ['48%', '68%'],
-    distributionPieCenterY: compact ? '62%' : '57%',
+    distributionPieRadius: compact ? ['38%', '50%'] : ['40%', '52%'],
+    distributionPieCenterY: compact ? '60%' : '57%',
     distributionLegendBottom: compact ? 0 : 10,
     distributionLegendItemSize: compact ? 9 : 13,
-    distributionLegendGap: compact ? 8 : 14,
+    distributionLegendGap: compact ? 4 : 10,
     distributionLegendFontSize: compact ? 10 : 14,
     trendGridTop: compact ? 10 : 32,
     trendGridRight: compact ? 12 : THEME.grid.right,
@@ -129,24 +158,34 @@ function getChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
   }
 }
 
-function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
+function getDistributionOption(
+  data: InventoryItem[],
+  totals?: Pick<InventoryDistributionResponse, 'totalInventory' | 'totalOverStock'>
+): echarts.EChartsOption {
   const layout = getChartLayout(distributionChartRef)
-  const inventoryTotal = data.reduce((total, item) => total + item.mayInventoryAmount, 0)
-  const backlogTotal = data.reduce((total, item) => total + item.mayBacklogAmount, 0)
+  const distributionColors = [
+    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,
+    THEME.color.blue.light
+  ]
+  const inventoryTotal =
+    totals?.totalInventory != null
+      ? yuanToWan(totals.totalInventory)
+      : data.reduce((total, item) => total + item.mayInventoryAmount, 0)
+  const backlogTotal =
+    totals?.totalOverStock != null
+      ? yuanToWan(totals.totalOverStock)
+      : data.reduce((total, item) => total + item.mayBacklogAmount, 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,
-      THEME.color.blue.light
-    ],
+    color: distributionColors,
     tooltip: createTooltip({
       trigger: 'item',
       formatter(params: any) {
@@ -157,9 +196,9 @@ function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
     }),
     title: [
       {
-        text: `5月底余额\n${formatAmount(inventoryTotal)} 万元`,
-        left: '26.5%',
-        top: layout.distributionTitleTop,
+        text: '各单位存货占比分布',
+        left: '20.5%',
+        top: 4,
         textAlign: 'center',
         textStyle: {
           color: THEME.text.strong,
@@ -170,9 +209,9 @@ function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
         }
       },
       {
-        text: `5月底积压\n${formatAmount(backlogTotal)} 万元`,
-        left: '72.5%',
-        top: layout.distributionTitleTop,
+        text: '各单位积压占比分布',
+        left: '69.5%',
+        top: 4,
         textAlign: 'center',
         textStyle: {
           color: THEME.text.strong,
@@ -198,61 +237,186 @@ function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
         fontFamily: FONT_FAMILY
       }
     }),
+    graphic: [
+      {
+        type: 'text',
+        left: '19.5%',
+        top: '45%',
+        style: {
+          text: `总库存\n${formatAmount(inventoryTotal)}万元`,
+          textAlign: 'center',
+          fill: THEME.text.strong,
+          fontSize: layout.compact ? 8 : 10,
+          fontWeight: 700,
+          lineHeight: layout.distributionTitleLineHeight + 6,
+          fontFamily: FONT_FAMILY
+        }
+      },
+      {
+        type: 'text',
+        left: '67%',
+        top: '45%',
+        style: {
+          text: `总积压\n${formatAmount(backlogTotal)}万元`,
+          textAlign: 'center',
+          fill: THEME.text.strong,
+          fontSize: layout.compact ? 8 : 10,
+          fontWeight: 700,
+          lineHeight: layout.distributionTitleLineHeight + 6,
+          fontFamily: FONT_FAMILY
+        }
+      }
+    ],
     series: [
       {
-        name: '5月底余额',
+        name: '库存',
         type: 'pie',
         radius: layout.distributionPieRadius,
-        center: ['27%', layout.distributionPieCenterY],
+        center: ['25%', layout.distributionPieCenterY],
         minAngle: 5,
+        avoidLabelOverlap: false,
         label: {
-          show: false
+          show: true,
+          position: 'outside',
+          alignTo: 'labelLine',
+          bleedMargin: 0,
+          formatter: '{b}\n{d}%',
+          color: 'inherit',
+          fontSize: layout.labelFontSize,
+          fontWeight: 500,
+          fontFamily: FONT_FAMILY
         },
-        data: data.map((item) => ({
+        labelLine: {
+          show: true,
+          // length: layout.compact ? 6 : 10,
+          // length2: layout.compact ? 8 : 14,
+          minTurnAngle: 90,
+          lineStyle: {
+            color: distributionColors[0],
+            width: 1.5,
+            opacity: 1
+          }
+        },
+        labelLayout: {
+          hideOverlap: false
+        },
+        data: data.map((item, index) => ({
           name: item.project,
-          value: item.mayInventoryAmount
+          value: item.mayInventoryAmount,
+          itemStyle: {
+            color: distributionColors[index % distributionColors.length]
+          },
+          label: {
+            color: distributionColors[index % distributionColors.length]
+          },
+          labelLine: {
+            show: true,
+            lineStyle: {
+              color: distributionColors[index % distributionColors.length],
+              width: 1.5,
+              opacity: 1
+            }
+          }
         }))
       },
       {
-        name: '5月底积压',
+        name: '积压库存',
         type: 'pie',
         radius: layout.distributionPieRadius,
         center: ['73%', layout.distributionPieCenterY],
         minAngle: 5,
+        avoidLabelOverlap: false,
         label: {
-          show: false
+          show: true,
+          position: 'outside',
+          alignTo: 'labelLine',
+          bleedMargin: 0,
+          formatter: '{b}\n{d}%',
+          color: 'inherit',
+          fontSize: layout.labelFontSize,
+          fontWeight: 500,
+          fontFamily: FONT_FAMILY
+        },
+        labelLine: {
+          show: true,
+          // length: layout.compact ? 6 : 10,
+          // length2: layout.compact ? 8 : 14,
+          minTurnAngle: 90,
+          lineStyle: {
+            color: distributionColors[0],
+            width: 1.5,
+            opacity: 1
+          }
+        },
+        labelLayout: {
+          hideOverlap: false
         },
         data: data
           .filter((item) => item.mayBacklogAmount > 0)
-          .map((item) => ({
+          .map((item, index) => ({
             name: item.project,
-            value: item.mayBacklogAmount
+            value: item.mayBacklogAmount,
+            itemStyle: {
+              color: distributionColors[index % distributionColors.length]
+            },
+            label: {
+              color: distributionColors[index % distributionColors.length]
+            },
+            labelLine: {
+              show: true,
+              lineStyle: {
+                color: distributionColors[index % distributionColors.length],
+                width: 1.5,
+                opacity: 1
+              }
+            }
           }))
       }
     ]
   }
 }
 
-function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
-  const layout = getChartLayout(trendChartRef)
-  const maxBacklog = Math.max(
-    ...data.map((item) => Math.max(item.yearBeginningBacklog, item.mayBacklogAmount)),
-    1
-  )
-  const backlogAxisMax = Math.ceil((maxBacklog * 1.15) / 100) * 100
-  const barLabel = {
-    show: true,
-    position: 'right' as any,
-    distance: layout.labelDistance,
-    color: THEME.text.strong,
-    fontSize: layout.labelFontSize,
-    fontWeight: 700,
-    fontFamily: FONT_FAMILY,
-    formatter(params: any) {
-      return formatAmount(Number(params.value))
-    }
+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 getInventoryAgingPoints(data: unknown): InventoryAgingPoint[] {
+  if (Array.isArray(data)) {
+    return data
+      .map((item: any) => ({
+        month: item?.month ?? item?.date ?? item?.fdate,
+        value: Number(item?.value ?? item?.amount ?? item?.overStockAmount)
+      }))
+      .filter((item) => /^\d{4}-\d{2}$/.test(item.month) && Number.isFinite(item.value))
+      .sort((itemA, itemB) => itemA.month.localeCompare(itemB.month))
   }
 
+  if (!data || typeof data !== 'object') return []
+
+  return Object.entries(data)
+    .map(([month, value]) => ({ month, value: Number(value) }))
+    .filter((item) => /^\d{4}-\d{2}$/.test(item.month) && Number.isFinite(item.value))
+    .sort((itemA, itemB) => itemA.month.localeCompare(itemB.month))
+}
+
+function getInventoryAgingOption(data: unknown): echarts.EChartsOption {
+  const layout = getChartLayout(trendChartRef)
+  const entries = getInventoryAgingPoints(data)
+  const months = entries.map(({ month }) => month)
+  const values = entries.map(({ value }) => yuanToWan(value))
+
   return {
     ...ANIMATION,
     grid: {
@@ -261,45 +425,28 @@ function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
       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
-        }
-      },
-      ['年初积压', '5月底积压']
-    ),
     tooltip: createTooltip({
       trigger: 'axis',
-      axisPointer: {
-        type: 'shadow',
-        shadowStyle: {
-          color: THEME.split
-        }
-      },
       valueFormatter(value: number) {
         return `${formatAmount(value)}万元`
       }
     }),
     xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: months,
+      axisTick: { show: false },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: layout.axisFontSize,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    yAxis: {
       type: 'value',
-      max: backlogAxisMax,
-      splitNumber: 4,
-      axisLine: {
-        show: false
-      },
-      axisTick: {
-        show: false
-      },
+      name: '万元',
+      min: 0,
+      axisTick: { show: false },
       axisLabel: {
         color: THEME.text.regular,
         fontSize: layout.axisFontSize,
@@ -312,115 +459,73 @@ function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
         }
       }
     },
-    yAxis: {
-      type: 'category',
-      data: data.map((item) => item.project),
-      inverse: true,
-      axisLine: {
-        show: false
-      },
-      axisTick: {
-        show: false
-      },
-      axisLabel: {
-        color: THEME.text.regular,
-        fontSize: layout.axisFontSize,
-        fontWeight: 600,
-        fontFamily: FONT_FAMILY,
-        margin: layout.yAxisLabelMargin,
-        width: layout.yAxisLabelWidth,
-        overflow: 'break',
-        align: 'right'
-      }
-    },
     series: [
       {
-        name: '年初积压',
-        type: 'bar',
-        data: data.map((item) => item.yearBeginningBacklog),
-        barWidth: layout.barWidth,
-        barGap: layout.barGap,
-        barCategoryGap: layout.barCategoryGap,
-        label: barLabel,
-        labelLayout: {
-          hideOverlap: false
+        name: '积压库存',
+        type: 'line',
+        smooth: true,
+        data: values,
+        symbol: 'circle',
+        symbolSize: layout.compact ? 6 : 8,
+        lineStyle: {
+          width: layout.compact ? 2 : 3,
+          color: THEME.color.orange.line
         },
         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: layout.barWidth,
-        barGap: layout.barGap,
-        barCategoryGap: layout.barCategoryGap,
-        label: barLabel,
-        labelLayout: {
-          hideOverlap: false
+          color: THEME.color.orange.line
         },
-        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 }
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgb(255 161 79 / 28%)' },
+            { offset: 1, color: 'rgb(255 161 79 / 2%)' }
           ])
+        },
+        label: {
+          show: true,
+          color: THEME.color.orange.line,
+          fontSize: layout.labelFontSize,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return formatAmount(Number(params.value))
+          }
         }
       }
     ]
   }
 }
 
-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)
+  const data = getFactoryInventoryData(inventoryResult.value)
+  distributionChart?.setOption(
+    getDistributionOption(data, inventoryResult.value || undefined),
+    true
+  )
 }
 
 function renderTrendChart() {
-  trendChart?.setOption(getTrendOption(inventoryData), true)
+  trendChart?.setOption(getInventoryAgingOption(inventoryAgingResult.value), true)
 }
 
 function initDistributionChart() {
+  const data = getFactoryInventoryData(inventoryResult.value)
   distributionChart = initChart(
     distributionChartRef,
     distributionChart,
-    getDistributionOption(inventoryData)
+    getDistributionOption(data, inventoryResult.value || undefined)
   )
 }
 
 function initTrendChart() {
-  trendChart = initChart(trendChartRef, trendChart, getTrendOption(inventoryData))
+  trendChart = initChart(
+    trendChartRef,
+    trendChart,
+    getInventoryAgingOption(inventoryAgingResult.value)
+  )
 }
 
 function resizeCharts() {
   distributionChart?.resize()
   trendChart?.resize()
-  renderDistributionChart()
-  renderTrendChart()
 }
 
 function destroyCharts() {
@@ -435,20 +540,103 @@ watch(activePanel, (value) => {
     if (value === 'distribution') {
       if (!distributionChart) initDistributionChart()
       renderDistributionChart()
+    } else if (value === 'trend') {
+      requestInventoryAging()
     } else {
-      if (!trendChart) initTrendChart()
-      renderTrendChart()
+      requestInventoryDetail()
     }
-    resizeCharts()
   })
 })
 
+async function requestInventoryAging() {
+  if (deptId.value === null || !selectedDate.value) return
+
+  try {
+    const response = await IotStatApi.getInventoryAging({
+      fdate: selectedDate.value,
+      dept: deptId.value
+    })
+    const data = response?.data?.data ?? response?.data ?? response
+
+    const points = getInventoryAgingPoints(data)
+
+    console.log('库存积压趋势数据', points)
+
+    if (points.length) {
+      inventoryAgingResult.value = Object.fromEntries(
+        points.map(({ month, value }) => [month, value])
+      )
+      nextTick(() => {
+        if (!trendChart) initTrendChart()
+        renderTrendChart()
+      })
+    } else if (trendChart) {
+      trendChart.setOption(getInventoryAgingOption({}), true)
+    }
+  } catch (error) {
+    console.error('获取库存积压趋势数据失败', error)
+  }
+}
+
+async function requestInventoryDistribution() {
+  if (deptId.value === null || !selectedDate.value) return
+
+  try {
+    const response = await IotStatApi.getInventoryDistribution({
+      fdate: selectedDate.value,
+      dept: deptId.value
+    })
+    const data = response
+
+    if (data && typeof data === 'object') {
+      inventoryResult.value = data as InventoryDistributionResponse
+      renderDistributionChart()
+    }
+  } catch (error) {
+    console.error('获取库存分布数据失败', error)
+  }
+}
+
+async function requestInventoryDetail() {
+  if (deptId.value === null || !selectedDate.value) return
+
+  try {
+    const response = await IotStatApi.getInventoryDetail({
+      fdate: selectedDate.value,
+      dept: deptId.value
+    })
+    const data = response?.data?.data ?? response?.data ?? response
+
+    if (data && typeof data === 'object') {
+      inventoryDetailResult.value = data as InventoryDetail
+    }
+  } catch (error) {
+    console.error('获取库存明细数据失败', error)
+  }
+}
+
 onMounted(() => {
   initDistributionChart()
   window.addEventListener('resize', resizeCharts)
   window.addEventListener('rhkb:resize', resizeCharts)
 })
 
+let deptId = ref<number | string>('157')
+
+onMounted(async () => {
+  await requestInventoryDistribution()
+})
+
+watch(selectedDate, () => {
+  if (activePanel.value === 'distribution') {
+    requestInventoryDistribution()
+  } else if (activePanel.value === 'trend') {
+    requestInventoryAging()
+  } else {
+    requestInventoryDetail()
+  }
+})
+
 onUnmounted(() => {
   window.removeEventListener('resize', resizeCharts)
   window.removeEventListener('rhkb:resize', resizeCharts)
@@ -466,18 +654,69 @@ onUnmounted(() => {
         </div>
         {{ activeTitle }}
       </div>
-      <el-segmented
-        v-model="activePanel"
-        :options="panelOptions"
-        size="small"
-        class="inventory-switch" />
+      <div class="inventory-panel-actions">
+        <el-date-picker
+          v-model="selectedDate"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择日期"
+          :clearable="false"
+          style="width: 120px"
+          class="inventory-date-picker" />
+        <el-segmented
+          v-model="activePanel"
+          :options="panelOptions"
+          size="small"
+          class="inventory-switch" />
+      </div>
     </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
+        v-show="activePanel === 'trend'"
+        ref="trendChartRef"
+        style="height: 80%; margin-top: 18px"
+        class="inventory-chart"></div>
+      <div v-show="activePanel === 'detail'" class="inventory-detail-layout">
+        <section class="inventory-detail-section">
+          <div class="inventory-detail-section__title">存货及变动情况</div>
+          <el-table :data="detailTableData" border class="inventory-detail-table">
+            <el-table-column
+              prop="beginningInventory"
+              label="年初存货"
+              min-width="92"
+              align="center" />
+            <el-table-column
+              prop="beginningOverStock"
+              label="年初积压"
+              min-width="92"
+              align="center" />
+            <el-table-column
+              prop="endingInventory"
+              label="期末库存"
+              min-width="92"
+              align="center" />
+            <el-table-column
+              prop="endingOverStock"
+              label="期末积压"
+              min-width="92"
+              align="center" />
+          </el-table>
+        </section>
+        <section class="inventory-detail-section">
+          <div class="inventory-detail-section__title">账龄分析</div>
+          <el-table :data="detailTableData" border class="inventory-detail-table">
+            <el-table-column prop="withinOneYear" label="1年以内" min-width="82" align="center" />
+            <el-table-column prop="oneToTwoYears" label="1-2年" min-width="74" align="center" />
+            <el-table-column prop="twoToThreeYears" label="2-3年" min-width="74" align="center" />
+            <el-table-column prop="overThreeYears" label="3年以上" min-width="82" align="center" />
+            <el-table-column prop="overStockRatio" label="积压比" min-width="78" align="center" />
+          </el-table>
+        </section>
+      </div>
     </div>
   </div>
 </template>
@@ -485,6 +724,12 @@ onUnmounted(() => {
 <style lang="scss" scoped>
 @import url('@/styles/kb.scss');
 
+.inventory-panel-actions {
+  display: flex;
+  align-items: center;
+  gap: calc(8px * var(--kb-scale, 1));
+}
+
 .inventory-switch {
   --el-segmented-item-selected-color: #03409b;
   --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
@@ -494,13 +739,29 @@ onUnmounted(() => {
   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;
+    font-size: calc(12px * var(--kb-scale, 1));
+    font-weight: 500;
+    color: #29527f;
+  }
+}
+
+.inventory-date-picker {
+  width: calc(132px * var(--kb-scale, 1));
+
+  :deep(.el-input__wrapper) {
+    min-height: calc(26px * var(--kb-scale, 1));
+    padding: 0 calc(7px * var(--kb-scale, 1));
+    background: rgb(255 255 255 / 82%);
+    border-radius: calc(4px * var(--kb-scale, 1));
+    box-shadow: 0 0 0 1px rgb(31 91 184 / 24%) inset;
+  }
+
+  :deep(.el-input__inner) {
+    font-size: calc(12px * var(--kb-scale, 1));
     color: #29527f;
   }
 }
@@ -510,4 +771,105 @@ onUnmounted(() => {
   height: 100%;
   min-height: 0;
 }
+
+.inventory-detail-table {
+  width: 100%;
+  height: auto;
+  margin: 0;
+  color: #1d2f46;
+  background: transparent;
+
+  :deep(.el-table__inner-wrapper::before) {
+    background-color: rgb(31 91 184 / 28%);
+  }
+
+  :deep(th.el-table__cell) {
+    padding: calc(3px * var(--kb-scale, 1)) 0;
+    color: #073f8e;
+    font-size: calc(13px * var(--kb-scale, 1));
+    font-weight: 700;
+    text-align: center;
+    background: transparent;
+    border-color: rgb(31 91 184 / 24%);
+  }
+
+  :deep(th.el-table__cell .cell) {
+    justify-content: center;
+    text-align: center;
+  }
+
+  :deep(td.el-table__cell) {
+    padding: calc(4px * var(--kb-scale, 1)) 0;
+    color: #1d2f46;
+    font-size: calc(13px * var(--kb-scale, 1));
+    border-color: rgb(31 91 184 / 20%);
+  }
+
+  :deep(.el-table__body tr:hover > td.el-table__cell) {
+    background-color: transparent;
+  }
+
+  :deep(td.el-table__cell) {
+    background: transparent;
+  }
+}
+
+.inventory-detail-layout {
+  display: grid;
+  height: 100%;
+  padding-top: calc(10px * var(--kb-scale, 1));
+  gap: calc(12px * var(--kb-scale, 1));
+  grid-template-rows: repeat(2, minmax(0, 1fr));
+}
+
+.inventory-detail-section {
+  display: flex;
+  min-height: 0;
+  flex-direction: column;
+}
+
+.inventory-detail-section__title {
+  padding: 0 0 calc(5px * var(--kb-scale, 1));
+  font-size: calc(13px * var(--kb-scale, 1));
+  font-weight: 700;
+  color: #0b4a9c;
+  text-align: center;
+}
+
+.inventory-detail-section .inventory-detail-table {
+  flex: none;
+}
+
+::v-deep .el-table {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table th {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table tr {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table td {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table th.el-table__cell {
+  background-color: transparent !important;
+  border: none !important;
+}
+
+::v-deep .el-table .el-table__header-wrapper {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table thead {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table th.el-table__cell > .cell {
+  // color: rgba(255, 255, 255, 0.8) !important;
+}
 </style>