InventoryTurnoverView.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <template>
  2. <div class="app-page">
  3. <section class="glass-card section-card">
  4. <el-form :model="filters" inline class="filter-form">
  5. <el-form-item label="SKU">
  6. <el-input v-model="filters.sku" placeholder="SKU编号" clearable style="width:160px" @keyup.enter="loadData" />
  7. </el-form-item>
  8. <el-form-item label="仓库">
  9. <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width:140px">
  10. <el-option v-for="w in warehouses" :key="w" :label="w" :value="w" />
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="分析维度">
  14. <el-select v-model="filters.dimension" placeholder="全部" clearable style="width:130px">
  15. <el-option label="按SKU" value="sku" />
  16. <el-option label="按类目" value="category" />
  17. <el-option label="按仓库" value="warehouse" />
  18. </el-select>
  19. </el-form-item>
  20. <el-form-item>
  21. <el-button type="primary" @click="loadData">查询</el-button>
  22. <el-button @click="resetFilters">重置</el-button>
  23. </el-form-item>
  24. </el-form>
  25. </section>
  26. <section class="glass-card section-card" style="padding:12px 24px">
  27. <div class="table-toolbar" style="margin-bottom:0">
  28. <div class="chip-list">
  29. <el-button type="primary" @click="setAlert">设置预警</el-button>
  30. <el-button @click="doExport">导出报表</el-button>
  31. </div>
  32. <el-button @click="loadData">刷新</el-button>
  33. </div>
  34. </section>
  35. <section class="glass-card section-card" style="padding:16px">
  36. <div class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
  37. <article class="stat-card">
  38. <div class="stat-card__label">库存周转天数</div>
  39. <div class="stat-card__value" style="font-size:24px;color:var(--cb-primary)">28天</div>
  40. </article>
  41. <article class="stat-card">
  42. <div class="stat-card__label">动销率</div>
  43. <div class="stat-card__value" style="font-size:24px">76%</div>
  44. </article>
  45. <article class="stat-card">
  46. <div class="stat-card__label">滞销SKU</div>
  47. <div class="stat-card__value" style="font-size:24px;color:var(--cb-accent)">15</div>
  48. </article>
  49. <article class="stat-card">
  50. <div class="stat-card__label">库存覆盖天数</div>
  51. <div class="stat-card__value" style="font-size:24px">42天</div>
  52. </article>
  53. <article class="stat-card">
  54. <div class="stat-card__label">平均库龄</div>
  55. <div class="stat-card__value" style="font-size:24px">35天</div>
  56. </article>
  57. </div>
  58. </section>
  59. <section class="glass-card section-card">
  60. <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading">
  61. <el-table-column prop="sku" label="SKU" width="160" />
  62. <el-table-column prop="productTitle" label="商品名称" min-width="200" show-overflow-tooltip />
  63. <el-table-column prop="warehouse" label="仓库" width="120" />
  64. <el-table-column prop="available" label="可用库存" width="90" align="center" />
  65. <el-table-column prop="sales30d" label="30天销量" width="90" align="center" />
  66. <el-table-column prop="turnoverDays" label="周转天数" width="90" align="center">
  67. <template #default="{ row }">
  68. <span :class="turnoverClass(row.turnoverDays)">{{ row.turnoverDays }}天</span>
  69. </template>
  70. </el-table-column>
  71. <el-table-column prop="stockDays" label="库存覆盖天数" width="110" align="center">
  72. <template #default="{ row }">
  73. <span :class="stockDaysClass(row.stockDays)">{{ row.stockDays }}天</span>
  74. </template>
  75. </el-table-column>
  76. <el-table-column prop="avgAge" label="平均库龄" width="90" align="center">
  77. <template #default="{ row }">
  78. {{ row.avgAge }}天
  79. </template>
  80. </el-table-column>
  81. <el-table-column prop="status" label="状态" width="90">
  82. <template #default="{ row }">
  83. <el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag>
  84. </template>
  85. </el-table-column>
  86. <el-table-column label="操作" width="120" fixed="right">
  87. <template #default="{ row }">
  88. <el-button link type="primary" @click="openDetail(row)">详情</el-button>
  89. <el-button link type="primary" @click="createOrder(row)">补货</el-button>
  90. </template>
  91. </el-table-column>
  92. <template #empty>
  93. <el-empty description="暂无数据" />
  94. </template>
  95. </el-table>
  96. </section>
  97. </div>
  98. </template>
  99. <script setup lang="ts">
  100. import { computed, onMounted, ref } from 'vue';
  101. import { ElMessage } from 'element-plus';
  102. interface TurnoverItem {
  103. sku: string;
  104. productTitle: string;
  105. warehouse: string;
  106. available: number;
  107. sales30d: number;
  108. turnoverDays: number;
  109. stockDays: number;
  110. avgAge: number;
  111. status: string;
  112. }
  113. const items = ref<TurnoverItem[]>([
  114. { sku: 'SKU-LUGG-20-BLK', productTitle: 'TravelFlex Carry-On / Black', warehouse: '深圳南山仓', available: 15, sales30d: 240, turnoverDays: 15, stockDays: 18, avgAge: 20, status: '正常' },
  115. { sku: 'SKU-BAG-ML-BRW', productTitle: 'Classic Leather Tote / Brown', warehouse: '义乌商贸仓', available: 45, sales30d: 150, turnoverDays: 28, stockDays: 30, avgAge: 25, status: '正常' },
  116. { sku: 'SKU-SPRT-YGA-BLU', productTitle: 'Yoga Mat Pro / Blue', warehouse: '深圳南山仓', available: 20, sales30d: 360, turnoverDays: 8, stockDays: 5, avgAge: 12, status: '滞销' },
  117. { sku: 'SKU-LUGG-28-NVY', productTitle: 'TravelFlex Large Check-In / Navy', warehouse: '洛杉矶海外仓', available: 8, sales30d: 90, turnoverDays: 45, stockDays: 9, avgAge: 60, status: '滞销' },
  118. { sku: 'SKU-BAG-BPK-OLV', productTitle: 'Urban Backpack / Olive', warehouse: '义乌商贸仓', available: 55, sales30d: 180, turnoverDays: 32, stockDays: 30, avgAge: 40, status: '正常' },
  119. { sku: 'SKU-TOWEL-SET-MIX', productTitle: 'AeroDry Towel Set', warehouse: '深圳南山仓', available: 30, sales30d: 300, turnoverDays: 12, stockDays: 10, avgAge: 18, status: '正常' },
  120. { sku: 'SKU-SPRT-BTL-GRN', productTitle: 'Sports Bottle 750ml / Green', warehouse: '洛杉矶海外仓', available: 120, sales30d: 60, turnoverDays: 60, stockDays: 200, avgAge: 90, status: '严重滞销' }
  121. ]);
  122. const loading = ref(false);
  123. const warehouses = ['深圳南山仓', '义乌商贸仓', '洛杉矶海外仓'];
  124. const filters = ref({ sku: '', warehouse: '', dimension: '' });
  125. const filteredItems = computed(() => {
  126. return items.value.filter(item => {
  127. if (filters.value.sku && !item.sku.includes(filters.value.sku) && !item.productTitle.includes(filters.value.sku)) return false;
  128. if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
  129. return true;
  130. });
  131. });
  132. const turnoverClass = (days: number) => {
  133. if (days > 60) return 'text-danger';
  134. if (days > 30) return 'text-warning';
  135. return '';
  136. };
  137. const stockDaysClass = (days: number) => {
  138. if (days < 10) return 'text-danger';
  139. if (days < 20) return 'text-warning';
  140. return '';
  141. };
  142. const statusTag = (status: string) => {
  143. const map: Record<string, string> = { '正常': 'success', '滞销': 'warning', '严重滞销': 'danger' };
  144. return map[status] || '';
  145. };
  146. const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 300); };
  147. const resetFilters = () => { filters.value = { sku: '', warehouse: '', dimension: '' }; };
  148. const setAlert = () => { ElMessage.info('预警设置功能开发中'); };
  149. const doExport = () => { ElMessage.info('导出开始'); };
  150. const openDetail = (row: TurnoverItem) => { ElMessage.info(`查看 ${row.sku} 详情`); };
  151. const createOrder = (row: TurnoverItem) => { ElMessage.success(`已为 ${row.sku} 生成补货建议`); };
  152. onMounted(loadData);
  153. </script>
  154. <style scoped>
  155. .filter-form :deep(.el-form-item) { margin-bottom: 0; }
  156. .text-danger { color: var(--cb-danger); font-weight: 600; }
  157. .text-warning { color: var(--cb-accent); font-weight: 600; }
  158. </style>