Browse Source

feat(forecast-form): 添加前端分页功能并优化删除逻辑

yz 1 week ago
parent
commit
1585beccf7

+ 88 - 14
src/components/forecast-form/forecast-form-mixin.js

@@ -285,7 +285,13 @@ export default {
       selectedStockId: null,
 
       /** 当前库存 */
-      currentInventory: null
+      currentInventory: null,
+
+      // 分页状态
+      /** 当前页(从1开始) */
+      currentPage: 1,
+      /** 每页条数(默认10) */
+      pageSize: 10
     }
   },
 
@@ -305,6 +311,27 @@ export default {
         return this.title
       }
       return this.isEdit ? '编辑销售预测' : '新增销售预测'
+    },
+
+    /**
+     * 物料总数(用于分页 total)
+     * @returns {number}
+     */
+    total() {
+      return Array.isArray(this.stockTableData) ? this.stockTableData.length : 0
+    },
+
+    /**
+     * 当前页数据(前端分页)
+     * @returns {Array<import('@/api/types/order').PjpfStockDesc & { forecastQuantity: number, brandCode?: string, storeInventory?: string }>}
+     */
+    pagedStockTableData() {
+      const list = Array.isArray(this.stockTableData) ? this.stockTableData : []
+      const size = Number(this.pageSize) > 0 ? Number(this.pageSize) : 10
+      const page = Number(this.currentPage) > 0 ? Number(this.currentPage) : 1
+      const start = (page - 1) * size
+      const end = start + size
+      return list.slice(start, end)
     }
   },
 
@@ -444,6 +471,8 @@ export default {
         if (this.visible && !this.isEdit) {
           this.checkForecastByMonthAndEmit && this.checkForecastByMonthAndEmit()
         }
+        // 年份变更重置分页到第一页
+        this.currentPage = 1
       }
     },
     'formData.month': {
@@ -451,6 +480,8 @@ export default {
         if (this.visible && !this.isEdit) {
           this.checkForecastByMonthAndEmit && this.checkForecastByMonthAndEmit()
         }
+        // 月份变更重置分页到第一页
+        this.currentPage = 1
       }
     },
 
@@ -1237,41 +1268,46 @@ export default {
       this.stockTableData.push({ ...stock, forecastQuantity: 0 })
       // 清空已选
       this.selectedStockId = null
+
+      // 导入后保持当前页不变;无需校正页码(不会超过最大页)
     },
 
     /**
-     * 删除物料行
-     * @description 在下方物料表格中删除指定行,包含二次确认流程;删除后保持数据与UI同步。
-     * @param {import('./types').ForecastFormMixinData['stockTableData'][number]} row - 待删除的表格行数据
-     * @param {number} index - 行索引
+     * 删除物料行(分页适配)
+     * @param {import('./types').ForecastFormMixinData['stockTableData'][number]} row
+     * @param {number} index - 当前页内索引
      * @returns {Promise<void>}
-     * @this {import('./types').ForecastFormMixinComponent & Vue}
      */
     async handleDelete(row, index) {
       try {
-        // 索引校验,必要时根据唯一标识兜底定位
-        let removeIndex = typeof index === 'number' ? index : -1
-        if (removeIndex < 0 || removeIndex >= this.stockTableData.length) {
-          const keyId = row && (row.id != null ? row.id : row.goodsId)
+        // 先通过唯一键定位(优先 id,其次 goodsId)
+        const keyId = row && (row.id != null ? row.id : row.goodsId)
+        let removeIndex = -1
+        if (keyId != null) {
           removeIndex = this.stockTableData.findIndex(r => (r.id != null ? r.id : r.goodsId) === keyId)
         }
+        // 如果无唯一键或未找到,则按分页换算全局索引
+        if (removeIndex < 0 && typeof index === 'number') {
+          const globalIndex = (Math.max(1, Number(this.currentPage)) - 1) * Math.max(1, Number(this.pageSize)) + index
+          if (globalIndex >= 0 && globalIndex < this.stockTableData.length) {
+            removeIndex = globalIndex
+          }
+        }
         if (removeIndex < 0) {
           this.$message && this.$message.warning('未定位到要删除的记录')
           return
         }
 
-        // 二次确认
         await this.$confirm('确认删除该物料吗?删除后可重新通过上方选择器导入。', '提示', {
           type: 'warning',
           confirmButtonText: '删除',
           cancelButtonText: '取消'
         })
 
-        // 使用 Vue.set/delete 保持响应式
         this.$delete(this.stockTableData, removeIndex)
 
-        // 如有需要,清理与该行相关的临时状态(当前实现无行级临时状态)
-        // 例如:this.currentInventory = null
+        // 删除后校正页码:若当前页无数据则回退到上一页
+        this.normalizePageAfterMutations()
 
         this.$message && this.$message.success('已删除')
       } catch (e) {
@@ -1284,6 +1320,44 @@ export default {
     },
 
     /**
+     * 页容量变更(el-pagination: size-change)
+     * @param {number} size
+     * @returns {void}
+     */
+    handleSizeChange(size) {
+      const newSize = Number(size) > 0 ? Number(size) : 10
+      this.pageSize = newSize
+      // 变更每页大小后,将页码重置为 1
+      this.currentPage = 1
+    },
+
+    /**
+     * 页码变更(el-pagination: current-change)
+     * @param {number} page
+     * @returns {void}
+     */
+    handlePageChange(page) {
+      const newPage = Number(page) > 0 ? Number(page) : 1
+      this.currentPage = newPage
+    },
+
+    /**
+     * 变更后校正页码(删除/导入后调用)
+     * @returns {void}
+     */
+    normalizePageAfterMutations() {
+      const total = this.total
+      const size = Math.max(1, Number(this.pageSize) || 10)
+      const maxPage = Math.max(1, Math.ceil(total / size))
+      if (this.currentPage > maxPage) {
+        this.currentPage = maxPage
+      }
+      if (this.currentPage < 1) {
+        this.currentPage = 1
+      }
+    },
+
+    /**
      * 品牌变更处理
      * @param {number} brandId - 品牌ID
      * @returns {void}

+ 20 - 5
src/components/forecast-form/index.vue

@@ -82,7 +82,7 @@
           </div>
 
           <el-table
-            :data="stockTableData"
+            :data="pagedStockTableData"
             border
             stripe
             height="360"
@@ -122,6 +122,20 @@
               </template>
             </el-table-column>
           </el-table>
+
+          <!-- 分页控件(前端分页) -->
+          <div class="table-pagination" style="margin-top: 8px; text-align: right;">
+            <el-pagination
+              :current-page="currentPage"
+              :page-size="pageSize"
+              :page-sizes="[10, 20, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @current-change="handlePageChange"
+              @size-change="handleSizeChange"
+            />
+          </div>
+
           <div class="table-tip">提示:先在上方选择物料并点击“导入物料”,导入后的数据将显示在表格并参与保存流程。</div>
         </div>
       </div>
@@ -174,8 +188,8 @@ export default {
     this.$nextTick(() => {
       try {
         if (this.isEdit) {
-          if (this.$refs.yearPicker) this.$refs.yearPicker.disabled = true
-          if (this.$refs.monthSelect) this.$refs.monthSelect.disabled = true
+          if (this.$refs.yearPicker) { /** @type {any} */ (this.$refs.yearPicker).disabled = true }
+          if (this.$refs.monthSelect) { /** @type {any} */ (this.$refs.monthSelect).disabled = true }
         }
       } catch (e) {
         // 忽略可能的非关键性异常
@@ -185,11 +199,12 @@ export default {
 
   methods: {
     // 通过 $refs 统一设置“年份/月份”的禁用状态
+    /** @param {boolean} disabled */
     setYearMonthDisabled(disabled) {
       this.$nextTick(() => {
         try {
-          if (this.$refs.yearPicker) this.$refs.yearPicker.disabled = disabled
-          if (this.$refs.monthSelect) this.$refs.monthSelect.disabled = disabled
+          if (this.$refs.yearPicker) { /** @type {any} */ (this.$refs.yearPicker).disabled = disabled }
+          if (this.$refs.monthSelect) { /** @type {any} */ (this.$refs.monthSelect).disabled = disabled }
         } catch (e) {
           // 忽略可能的非关键性异常
         }

+ 26 - 1
src/components/forecast-form/types.d.ts

@@ -1,3 +1,10 @@
+/// <reference path="../../types/global.d.ts" />
+
+import type { AxiosResponse } from 'axios'
+import type { ApiResponse, PageResult, SelectOption, ValidationRule } from '@/api/types/forecast'
+import type { ItemRecord } from '@/api/types/common'
+import type { AvueFormColumn, AvueFormOption } from '@types/smallwei__avue/form'
+
 /**
  * 销售预测表单组件类型定义
  * @description 为销售预测表单相关组件提供完整的TypeScript类型支持
@@ -169,6 +176,11 @@ export interface ForecastFormMixinData {
   stockSelectOptions: Array<SelectOption<string>>;
   /** 当前选择待导入的物料ID */
   selectedStockId: string | null;
+
+  /** 分页:当前页(从1开始) */
+  currentPage: number;
+  /** 分页:每页条数 */
+  pageSize: number;
 }
 
 /**
@@ -234,6 +246,10 @@ export interface ForecastFormProps {
 export interface ForecastFormComputed {
   /** 表单标题 */
   formTitle: string;
+  /** 物料总数(用于分页total) */
+  total: number;
+  /** 当前页展示的数据(分页后) */
+  pagedStockTableData: Array<Partial<import('@/api/types/order').PjpfStockDesc> & { forecastQuantity: number; brandCode?: string; storeInventory?: string }>;
 }
 
 /**
@@ -280,8 +296,17 @@ export interface ForecastFormMethods {
   handleImportSelectedStock(): void;
   /** 删除物料行 */
   handleDelete(row: ForecastFormMixinData['stockTableData'][number], index: number): Promise<void>;
+  // 分页事件处理
+  /** 页面容量变更 */
+  handleSizeChange(size: number): void;
+  /** 页码变更 */
+  handlePageChange(page: number): void;
+  /** 变更后校正页码(删除/导入后) */
+  normalizePageAfterMutations(): void;
   /** 合并编辑回显行的库存数量 */
   mergeEchoStoreInventory(): Promise<void>;
+  /** 新增模式:检查指定年月是否已有预测,并通过事件通知父组件控制保存按钮禁用 */
+  checkForecastByMonthAndEmit(): Promise<void>;
   /** 提交(Avue 回调) */
   handleSubmit(): void;
   /** 重置 */
@@ -328,4 +353,4 @@ export interface CustomerSelectData {
 /**
  * 混入组件最终类型(给 JSDoc 使用)
  */
-export interface ForecastFormMixinComponent extends ForecastFormProps, ForecastFormMixinData, ForecastFormComputed, ForecastFormMethods, Vue {}
+export interface ForecastFormMixinComponent extends ForecastFormProps, ForecastFormMixinData, ForecastFormComputed, ForecastFormMethods, import('vue/types/vue').Vue {}