前端表单到 PDF 报价表:用对象化管理产品提升可维护性

8/4/2025, 6:01:02 PM

在生成报价表的过程中,旧代码用多个数组存储价格和数量,并通过魔法数字合并重复的配件数量,导致代码难以阅读和维护。本文介绍了如何将产品及配件视作对象,通过统一的 SKU 静态数组和组合映射,实现表单数据的智能合并与 PDF 渲染。优化后代码语义清晰,新增或修改产品无需调整核心逻辑,大幅提升可维护性和扩展性。

页面需求

用户填写他所需的各个产品数量,前端生成报价表 pdf 供用户下载。

原始数据结构及其缺陷

前端展示的效果是四个区块,每个区块由 1 个主产品 + 几个配件组成,其中有些配件在区块之间是重复出现的。导致用户填写所需数量的时候其实有 22 个框供其填写,但最终我们要合成 10 个 sku。

原始实现选择把价格和数量存为两个数组:

// 价格数组(已改写价格,但保持相同项相同)
const ASeriesPrices = [300, 50, 80, 30, 20, 10];       
const BSeriesPrices = [500, 50, 120, 30, 20, 10];    
const CSeriesPrices = [400, 50, 80, 30, 10];        
const D75SeriesPrices = [600, 50, 120, 30, 10];     
const productUnitPrices = [
  ...s1SeriesPrices,
  ...s175SeriesPrices,
  ...s1ProSeriesPrices,
  ...s1Pro75SeriesPrices,
];

// 渲染 pdf 的函数
export const generatePDF = (currency) => {
  ...
  // 把同一 sku 加起来
  productNumArrayPre[1] +=
    productNumArrayPre[7] + productNumArrayPre[13] + productNumArrayPre[18];
  productNumArrayPre[2] += productNumArrayPre[14];
  productNumArrayPre[3] +=
    productNumArrayPre[9] + productNumArrayPre[15] + productNumArrayPre[20];
  productNumArrayPre[4] += productNumArrayPre[10];
  productNumArrayPre[5] +=
    productNumArrayPre[11] + productNumArrayPre[16] + productNumArrayPre[21];
  productNumArrayPre[8] += productNumArrayPre[19];
  // 剔除掉不要的数据,剩下的数据用于渲染
  const productNumArray = productNumArrayPre.map((_, index) => {
    if ([7, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 21].includes(index)) {
      return 0;
    } else {
      return _;
    }
  });
  ...
};

这样的代码有两个问题:

  1. 可读性差。全是魔法数字,没办法一眼看出哪个数据对应哪个产品;
  2. 难以维护。不管是改现有产品还是添加新产品,价格数组和合成 sku 的部分都要调整多处。

用对象描述产品

其实前面的问题就是把产品的属性拆开来看了。当需要数据的时候不仅要在几个数组之间找,还对顺序有硬性要求。优化思路就是把产品视作对象,需要数据的时候直接去找那个对象,然后获取它身上你需要的属性即可。

// 静态对象数组,接下来的数据都从这里取来加工
const SKUS = [
  { id: 'productA', name: '产品A', price: 300, type: 'product' },
  ...
  { id: 'accessoriesB', name: '配件B', price: 10, type: 'accessories' }
];

// 用于渲染表单的数组
const PRODUCT_COMBINATIONS = [
  ['productA', ..., 'accessoriesB'],
  ...
  ['productD', ..., 'accessoriesB'],
];

// 表单状态(实际由 rc-field-form 管理)
const [quantities, setQuantities] = useState({
  // key: `${组合索引}-${skuId}`
  '0-productA': 0,
  ...
  '3-accessoriesB': 0,
});

// 把同一 sku 加起来
const totalSkuCounts = {};

for (let key in quantities) {
  const [, skuId] = key.split('-');
  totalSkuCounts[skuId] = (totalSkuCounts[skuId] || 0) + quantities[key];
}

此时语义就很明显了,代码所做的事就是从表单状态中逐个取出 skuId,并把 skuId 相同的数量加起来。要修改或添加产品只需要修改静态对象数组,余下的逻辑代码都不用动!