Gogs 3 ماه پیش
والد
کامیت
9db4b0bf19
1فایلهای تغییر یافته به همراه230 افزوده شده و 33 حذف شده
  1. 230 33
      src/views/storage/overview/index.vue

+ 230 - 33
src/views/storage/overview/index.vue

@@ -8,10 +8,9 @@
               <i class="el-icon-box"></i>
             </div>
             <div class="metric-info">
-              <div class="metric-label">总体库存量</div>
+              <div class="metric-label">总体库存量(成品)</div>
               <div class="metric-value">{{ overviewData.totalInventory }}</div>
               <div class="metric-unit">件</div>
-              <div class="metric-sub">含组装转入 {{ overviewData.assemblyQty || 0 }} 件</div>
             </div>
           </div>
         </el-card>
@@ -23,7 +22,7 @@
               <i class="el-icon-money"></i>
             </div>
             <div class="metric-info">
-              <div class="metric-label">库存价值</div>
+              <div class="metric-label">库存价值(成品)</div>
               <div class="metric-value">{{ overviewData.totalValue }}</div>
               <div class="metric-unit">万元</div>
             </div>
@@ -37,7 +36,7 @@
               <i class="el-icon-refresh"></i>
             </div>
             <div class="metric-info">
-              <div class="metric-label">库存周转率</div>
+              <div class="metric-label">库存周转率(成品)</div>
               <div class="metric-value">{{ overviewData.turnoverRate }}</div>
               <div class="metric-unit">次</div>
             </div>
@@ -95,7 +94,7 @@
       <el-col :span="24">
         <el-card>
           <template slot="header">
-            <span>入库/销售/库存对比</span>
+            <span>成品入库/销售/库存对比</span>
           </template>
           <div ref="inventoryComparisonChart" class="chart" />
           <el-empty v-if="!monthlyLoading && monthlyComparison.length === 0" description="暂无数据" />
@@ -114,8 +113,37 @@
               </el-button>
             </div>
           </template>
-          <el-table :data="skuTableData" stripe style="width: 100%" v-loading="loading">
-            <el-table-column prop="sku" label="SKU" width="200" fixed />
+          <div class="sku-filters">
+            属性:
+            <el-select v-model="skuFilters.attribute" placeholder="属性" size="small" style="width: 160px">
+              <el-option label="全部" value="" />
+              <el-option v-for="item in skuAttributeOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+            分类:
+            <el-select v-model="skuFilters.category" placeholder="分类" size="small" style="width: 160px">
+              <el-option label="全部" value="" />
+              <el-option v-for="item in skuCategoryOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+            SPU:
+            <el-select v-model="skuFilters.spu" placeholder="SPU" size="small" style="width: 200px">
+              <el-option label="全部" value="" />
+              <el-option v-for="item in skuSpuOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+            <el-button size="small" @click="resetSkuFilters">重置</el-button>
+          </div>
+          <el-table
+            :data="pagedSkuData"
+            stripe
+            style="width: 100%"
+            v-loading="loading"
+            @sort-change="handleSkuSortChange"
+          >
+            <el-table-column prop="sku" label="SKU" width="200" fixed sortable="custom" />
+            <el-table-column prop="category" label="分类" width="140">
+              <template slot-scope="scope">
+                {{ scope.row.category || '-' }}
+              </template>
+            </el-table-column>
             <el-table-column prop="attribute" label="属性" width="140">
               <template slot-scope="scope">
                 {{ scope.row.attribute || '-' }}
@@ -126,32 +154,32 @@
                 {{ scope.row.spuName || '-' }}
               </template>
             </el-table-column>
-            <el-table-column prop="purchaseQty" label="入库数量" width="120" align="right">
+            <el-table-column prop="purchaseQty" label="入库数量" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.purchaseQty) }}
               </template>
             </el-table-column>
-            <el-table-column prop="salesQty" label="销售数量" width="120" align="right">
+            <el-table-column prop="salesQty" label="销售数量" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.salesQty) }}
               </template>
             </el-table-column>
-            <el-table-column prop="inventory" label="现有库存" width="120" align="right">
+            <el-table-column prop="inventory" label="现有库存" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.inventory) }}
               </template>
             </el-table-column>
-            <el-table-column prop="purchaseAmount" label="入库总金额" width="150" align="right">
+            <el-table-column prop="purchaseAmount" label="入库总金额" width="150" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.purchaseAmount) }}
               </template>
             </el-table-column>
-            <el-table-column prop="amountRatio" label="入库资金占比(%)" width="150" align="right">
+            <el-table-column prop="amountRatio" label="入库资金占比(%)" width="150" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ scope.row.amountRatio }}%
               </template>
             </el-table-column>
-            <el-table-column prop="turnoverRate" label="库存周转率" align="right">
+            <el-table-column prop="turnoverRate" label="库存周转率" align="right" sortable="custom">
               <template slot-scope="scope">
                 <el-tag :type="getTurnoverRateType(scope.row.turnoverRate)">
                   {{ scope.row.turnoverRate }}
@@ -159,6 +187,16 @@
               </template>
             </el-table-column>
           </el-table>
+          <el-pagination
+            :current-page="skuCurrentPage"
+            :page-size="skuPageSize"
+            :total="skuTotal"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            class="table-pagination"
+            @current-change="handleSkuPageChange"
+            @size-change="handleSkuSizeChange"
+          />
         </el-card>
       </el-col>
     </el-row>
@@ -174,40 +212,41 @@
               </el-button>
             </div>
           </template>
-          <el-table :data="spuTableData" stripe style="width: 100%" v-loading="spuLoading">
-            <el-table-column prop="spu" label="SPU" width="220" fixed />
-            <el-table-column prop="attribute" label="属性" width="140">
-              <template slot-scope="scope">
-                {{ scope.row.attribute || '-' }}
-              </template>
-            </el-table-column>
-            <el-table-column prop="skuCount" label="SKU数" width="90" align="right" />
-            <el-table-column prop="purchaseQty" label="入库数量" width="120" align="right">
+          <el-table
+            :data="pagedSpuData"
+            stripe
+            style="width: 100%"
+            v-loading="spuLoading"
+            @sort-change="handleSpuSortChange"
+          >
+            <el-table-column prop="spu" label="SPU" width="220" fixed sortable="custom" />
+            <el-table-column prop="skuCount" label="SKU数" width="90" align="right" sortable="custom" />
+            <el-table-column prop="purchaseQty" label="入库数量" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.purchaseQty) }}
               </template>
             </el-table-column>
-            <el-table-column prop="salesQty" label="销售数量" width="120" align="right">
+            <el-table-column prop="salesQty" label="销售数量" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.salesQty) }}
               </template>
             </el-table-column>
-            <el-table-column prop="inventory" label="现有库存" width="120" align="right">
+            <el-table-column prop="inventory" label="现有库存" width="120" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.inventory) }}
               </template>
             </el-table-column>
-            <el-table-column prop="purchaseAmount" label="入库总金额" width="150" align="right">
+            <el-table-column prop="purchaseAmount" label="入库总金额" width="150" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ formatNumber(scope.row.purchaseAmount) }}
               </template>
             </el-table-column>
-            <el-table-column prop="amountRatio" label="入库资金占比(%)" width="150" align="right">
+            <el-table-column prop="amountRatio" label="入库资金占比(%)" width="150" align="right" sortable="custom">
               <template slot-scope="scope">
                 {{ scope.row.amountRatio }}%
               </template>
             </el-table-column>
-            <el-table-column prop="turnoverRate" label="库存周转率" align="right">
+            <el-table-column prop="turnoverRate" label="库存周转率" align="right" sortable="custom">
               <template slot-scope="scope">
                 <el-tag :type="getTurnoverRateType(scope.row.turnoverRate)">
                   {{ scope.row.turnoverRate }}
@@ -215,6 +254,16 @@
               </template>
             </el-table-column>
           </el-table>
+          <el-pagination
+            :current-page="spuCurrentPage"
+            :page-size="spuPageSize"
+            :total="spuTotal"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            class="table-pagination"
+            @current-change="handleSpuPageChange"
+            @size-change="handleSpuSizeChange"
+          />
         </el-card>
       </el-col>
     </el-row>
@@ -307,6 +356,23 @@ export default {
       spuTableData: [],
       loading: false,
       spuLoading: false,
+      skuFilters: {
+        attribute: '',
+        category: '',
+        spu: ''
+      },
+      skuSort: {
+        prop: '',
+        order: ''
+      },
+      spuSort: {
+        prop: '',
+        order: ''
+      },
+      skuCurrentPage: 1,
+      skuPageSize: 10,
+      spuCurrentPage: 1,
+      spuPageSize: 10,
       chartInstance: null,
       uploadDialogVisible: false,
       uploadLoading: false,
@@ -344,6 +410,60 @@ export default {
       if (this.uploadTaskStatus === 'running') return '处理中'
       if (this.uploadTaskStatus === 'pending') return '排队中'
       return '任务状态'
+    },
+    skuAttributeOptions() {
+      const values = this.skuTableData
+        .map(item => (item.attribute || '').trim())
+        .filter(item => item)
+      return Array.from(new Set(values)).sort()
+    },
+    skuCategoryOptions() {
+      const values = this.skuTableData
+        .map(item => (item.category || '').trim())
+        .filter(item => item)
+      return Array.from(new Set(values)).sort()
+    },
+    skuSpuOptions() {
+      const values = this.skuTableData
+        .map(item => (item.spuName || '').trim())
+        .filter(item => item)
+      return Array.from(new Set(values)).sort()
+    },
+    filteredSkuData() {
+      return this.skuTableData.filter(row => {
+        if (this.skuFilters.attribute && row.attribute !== this.skuFilters.attribute) return false
+        if (this.skuFilters.category && row.category !== this.skuFilters.category) return false
+        if (this.skuFilters.spu && row.spuName !== this.skuFilters.spu) return false
+        return true
+      })
+    },
+    sortedSkuData() {
+      const { prop, order } = this.skuSort
+      if (!prop || !order) {
+        return this.filteredSkuData
+      }
+      return this.sortTableData(this.filteredSkuData, prop, order)
+    },
+    pagedSkuData() {
+      const start = (this.skuCurrentPage - 1) * this.skuPageSize
+      return this.sortedSkuData.slice(start, start + this.skuPageSize)
+    },
+    skuTotal() {
+      return this.filteredSkuData.length
+    },
+    pagedSpuData() {
+      const start = (this.spuCurrentPage - 1) * this.spuPageSize
+      return this.sortedSpuData.slice(start, start + this.spuPageSize)
+    },
+    spuTotal() {
+      return this.sortedSpuData.length
+    },
+    sortedSpuData() {
+      const { prop, order } = this.spuSort
+      if (!prop || !order) {
+        return this.spuTableData
+      }
+      return this.sortTableData(this.spuTableData, prop, order)
     }
   },
   mounted() {
@@ -353,6 +473,17 @@ export default {
     })
     window.addEventListener('resize', this.resizeChart)
   },
+  watch: {
+    'skuFilters.attribute'() {
+      this.skuCurrentPage = 1
+    },
+    'skuFilters.category'() {
+      this.skuCurrentPage = 1
+    },
+    'skuFilters.spu'() {
+      this.skuCurrentPage = 1
+    }
+  },
   beforeDestroy() {
     window.removeEventListener('resize', this.resizeChart)
     if (this.chartInstance) {
@@ -399,14 +530,14 @@ export default {
           trigger: 'axis',
           axisPointer: { type: 'shadow' }
         },
-        legend: { data: ['入库', '销售', '当前库存'] },
+        legend: { data: ['成品入库', '成品销售', '成品库存'] },
         grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
         xAxis: { type: 'category', data: months },
         yAxis: { type: 'value', name: '数量 (件)' },
         series: [
-          { name: '入库', type: 'bar', data: purchase, itemStyle: { color: '#5470c6' } },
-          { name: '销售', type: 'bar', data: sales, itemStyle: { color: '#91cc75' } },
-          { name: '当前库存', type: 'line', data: inventory, itemStyle: { color: '#fac858' }, lineStyle: { width: 3 }, smooth: true }
+          { name: '成品入库', type: 'bar', data: purchase, itemStyle: { color: '#5470c6' } },
+          { name: '成品销售', type: 'bar', data: sales, itemStyle: { color: '#91cc75' } },
+          { name: '成品库存', type: 'line', data: inventory, itemStyle: { color: '#fac858' }, lineStyle: { width: 3 }, smooth: true }
         ]
       }
       this.chartInstance.setOption(option, true)
@@ -449,7 +580,10 @@ export default {
       try {
         const res = await this.inventoryRequest({ url: '/api/inventory/sku-summary', method: 'get' })
         const data = this.normalizeResponse(res)
-        if (Array.isArray(data)) this.skuTableData = data
+        if (Array.isArray(data)) {
+          this.skuTableData = data
+          this.skuCurrentPage = 1
+        }
       } catch (error) {
         console.error('获取SKU汇总数据失败:', error)
       } finally {
@@ -461,7 +595,10 @@ export default {
       try {
         const res = await this.inventoryRequest({ url: '/api/inventory/spu-summary', method: 'get' })
         const data = this.normalizeResponse(res)
-        if (Array.isArray(data)) this.spuTableData = data
+        if (Array.isArray(data)) {
+          this.spuTableData = data
+          this.spuCurrentPage = 1
+        }
       } catch (error) {
         console.error('获取SPU汇总数据失败:', error)
       } finally {
@@ -607,6 +744,52 @@ export default {
     sleep(ms) {
       return new Promise(resolve => setTimeout(resolve, ms))
     },
+    sortTableData(source, prop, order) {
+      const direction = order === 'ascending' ? 1 : -1
+      const data = [...source]
+      data.sort((a, b) => {
+        const aRaw = a[prop]
+        const bRaw = b[prop]
+        const aNum = typeof aRaw === 'number' ? aRaw : Number(aRaw)
+        const bNum = typeof bRaw === 'number' ? bRaw : Number(bRaw)
+        const aIsNum = !Number.isNaN(aNum) && aRaw !== '' && aRaw !== null && aRaw !== undefined
+        const bIsNum = !Number.isNaN(bNum) && bRaw !== '' && bRaw !== null && bRaw !== undefined
+        if (aIsNum && bIsNum) {
+          return (aNum - bNum) * direction
+        }
+        return String(aRaw || '').localeCompare(String(bRaw || ''), 'zh-Hans-CN', { numeric: true }) * direction
+      })
+      return data
+    },
+    handleSkuSortChange({ prop, order }) {
+      this.skuSort = { prop, order }
+      this.skuCurrentPage = 1
+    },
+    handleSkuPageChange(page) {
+      this.skuCurrentPage = page
+    },
+    handleSkuSizeChange(size) {
+      this.skuPageSize = size
+      this.skuCurrentPage = 1
+    },
+    handleSpuPageChange(page) {
+      this.spuCurrentPage = page
+    },
+    handleSpuSizeChange(size) {
+      this.spuPageSize = size
+      this.spuCurrentPage = 1
+    },
+    handleSpuSortChange({ prop, order }) {
+      this.spuSort = { prop, order }
+      this.spuCurrentPage = 1
+    },
+    resetSkuFilters() {
+      this.skuFilters.attribute = ''
+      this.skuFilters.category = ''
+      this.skuFilters.spu = ''
+      this.skuSort = { prop: '', order: '' }
+      this.skuCurrentPage = 1
+    },
     getTurnoverRateType(rate) {
       if (rate === 0) return 'info'
       if (rate >= 1) return 'success'
@@ -733,6 +916,20 @@ export default {
   align-items: center;
 }
 
+.sku-filters {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 12px;
+  flex-wrap: wrap;
+}
+
+.table-pagination {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
 .chart {
   height: 300px;
 }