Browse Source

feat(forecast-form): 支持大数字ID和数量字段的字符串传输

yz 1 month ago
parent
commit
b345ac6e05
2 changed files with 65 additions and 33 deletions
  1. 3 3
      src/api/forecast/types.d.ts
  2. 62 30
      src/components/forecast-form/forecast-form-mixin.js

+ 3 - 3
src/api/forecast/types.d.ts

@@ -300,15 +300,15 @@ export type SalesForecastMainListResponse = Promise<AxiosResponse<ApiResponse<Pa
 
 // 新增:销售预测主表(main-add)- 子项请求体元素
 export interface SalesForecastMainAddItem {
-  brandId: number
+  brandId: number | string
   brandCode: string
   brandName: string
-  itemId: number
+  itemId: number | string
   itemCode: string
   itemName: string
   specs: string
   pattern: string
-  forecastQuantity: number
+  forecastQuantity: number | string
   /** 审批状态,沿用全局枚举定义 */
   approvalStatus: ApprovalStatus | number
 }

+ 62 - 30
src/components/forecast-form/forecast-form-mixin.js

@@ -1,4 +1,5 @@
 // @ts-check
+/* global BigInt */
 
 /**
  * @fileoverview 销售预测表单混入组件
@@ -58,7 +59,7 @@
 
 // API接口导入
 import { addForecast, updateForecast, getForecastDetail } from '@/api/forecast'
-import { batchSaveSalesForecastSummary } from '@/api/forecast/forecast-summary'
+import { addSalesForecastMain } from '@/api/forecast/forecast-summary'
 import { getUserLinkGoods } from '@/api/order/sales-order'
 
 // 常量和枚举导入
@@ -869,8 +870,9 @@ export default {
     },
 
     /**
-     * 提交表单数据(仅批量保存预测汇总
+     * 提交表单数据(main-add:提交销售预测主表及子项明细
      * @returns {Promise<void>}
+     * @this {ForecastFormMixinComponent & Vue}
      */
     async submitForm() {
       try {
@@ -880,59 +882,89 @@ export default {
           return
         }
 
-        // 组装批量保存载荷,仅保留预测数量>0的行
+        // 转换年份与月份
         const year = typeof this.formData.year === 'string' ? parseInt(this.formData.year, 10) : this.formData.year
-        const month = this.formData.month
+        const month = Number(this.formData.month)
+
+        // 安全的ID转换:优先使用 BigInt 校验范围,再决定以 number 还是 string 传输
+        const toIdOutput = (val) => {
+          if (val == null || val === '') return ''
+          try {
+            const bi = BigInt(String(val))
+            // 在 JS 中,超过 MAX_SAFE_INTEGER 的数字使用字符串传输,避免精度丢失
+            const absBi = bi >= 0n ? bi : -bi
+            const maxSafe = BigInt(Number.MAX_SAFE_INTEGER)
+            if (absBi <= maxSafe) {
+              // 安全范围内,返回 number
+              return Number(bi)
+            }
+            // 超出安全范围,返回字符串
+            return String(bi)
+          } catch (e) {
+            // 不是纯数字,兜底返回原字符串
+            return String(val)
+          }
+        }
 
-        const payload = this.stockTableData
+        // 安全的数值转换(用于数量等非ID字段):若不可安全表示整数,仍以字符串传输
+        const toSafeNumberOrString = (val) => {
+          if (val == null || val === '') return 0
+          if (typeof val === 'number') {
+            return Number.isFinite(val) ? val : String(val)
+          }
+          const parsed = Number(val)
+          return Number.isFinite(parsed) ? parsed : String(val)
+        }
+
+        // 组装子项明细,仅保留预测数量>0的行
+        const items = this.stockTableData
           .filter(row => Number(row.forecastQuantity) > 0)
           .map(row => {
             const matchedBrand = this.brandDescList.find(b => b.cname === row.brandName)
+            const rawBrandId = row.brandId != null && row.brandId !== '' ? row.brandId : (matchedBrand ? matchedBrand.id : '')
+            const rawItemId = row.goodsId
 
-            const brandIdRaw = row.brandId != null && row.brandId !== ''
-              ? row.brandId
-              : (matchedBrand ? matchedBrand.id : null)
-            const itemIdRaw = row.goodsId
-
-            const toSafeNumberOrString = (val) => {
-              if (val == null || val === '') return 0
-              if (typeof val === 'number') {
-                return Number.isSafeInteger(val) ? val : String(val)
-              }
-              const parsed = Number(val)
-              return Number.isSafeInteger(parsed) ? parsed : String(val)
-            }
-
-            const brandId = toSafeNumberOrString(brandIdRaw)
-            const itemId = toSafeNumberOrString(itemIdRaw)
+            const brandId = toIdOutput(rawBrandId)
+            const itemId = toIdOutput(rawItemId)
 
             return {
-              year: year || new Date().getFullYear(),
-              month: month || (new Date().getMonth() + 1),
+              // ID与编码/名称:缺失则置空
               brandId: brandId,
-              brandCode: '',
+              brandCode: row.brandCode || '',
               brandName: row.brandName || (matchedBrand ? matchedBrand.cname : ''),
               itemId: itemId,
               itemCode: row.code || '',
               itemName: row.cname || '',
+              // 规格与花纹:无则空
               specs: row.typeNo || '',
               pattern: row.productDescription || row.brandItem || '',
-              forecastQuantity: Number(row.forecastQuantity) || 0,
-              approvalStatus: this.formData.approvalStatus || 0
+              // 数量:保留为 number(有限数),否则以字符串兜底
+              forecastQuantity: toSafeNumberOrString(row.forecastQuantity),
+              // 子项审批状态:与顶层保持一致,缺省为待提交
+              approvalStatus: Number(this.formData.approvalStatus ?? 0)
             }
           })
 
-        if (!payload.length) {
+        if (!items.length) {
           this.$message && this.$message.warning('请至少填写一条有效的预测数量')
           return
         }
 
-        const res = await batchSaveSalesForecastSummary(payload)
+        // 组装 main-add 载荷
+        const payload = {
+          year: year || new Date().getFullYear(),
+          month: month || (new Date().getMonth() + 1),
+          approvalStatus: Number(this.formData.approvalStatus ?? 0),
+          pcBladeSalesForecastSummaryList: items
+        }
+
+        const res = await addSalesForecastMain(payload)
         if (res && res.data && res.data.success) {
-          this.$message && this.$message.success('保存成功')
+          this.$message && this.$message.success('提交成功')
           this.$emit && this.$emit(FORECAST_FORM_EVENTS.SUBMIT, res.data)
+          this.$emit && this.$emit(FORECAST_FORM_EVENTS.SUBMIT_SUCCESS, res.data)
         } else {
-          const msg = (res && res.data && (res.data.msg || res.data.message)) || '保存失败'
+          const msg = (res && res.data && (res.data.msg || res.data.message)) || '提交失败'
           this.$message && this.$message.error(msg)
         }
       } catch (error) {