|
|
@@ -850,6 +850,7 @@
|
|
|
:disabled="quoteType === 2 || stockAttrDisabled"
|
|
|
v-model="scope.row.modelKey"
|
|
|
:isOne="scope.$index === 0"
|
|
|
+ :dedupe="true"
|
|
|
filterable
|
|
|
allow-create
|
|
|
default-first-option
|
|
|
@@ -865,6 +866,7 @@
|
|
|
:disabled="quoteType === 2 || stockAttrDisabled"
|
|
|
v-model="scope.row.colorKey"
|
|
|
:isOne="scope.$index === 0"
|
|
|
+ :dedupe="true"
|
|
|
filterable
|
|
|
allow-create
|
|
|
default-first-option
|
|
|
@@ -1103,6 +1105,88 @@
|
|
|
import { parameterGetByCode } from '@/api/main/index.js';
|
|
|
import { getSummaries } from '@/utils/util.js';
|
|
|
|
|
|
+ /**
|
|
|
+ * ==========================================================================
|
|
|
+ * 辅助数据请求调度器
|
|
|
+ * 销售订单新建/修改在“选品回填”时,会按每个物品去查商品信息
|
|
|
+ * (getGoodsByCategoryId)。一次选中几十上百个物品时,这些请求会
|
|
|
+ * 在同一个 tick 里全部并发发出,打满浏览器连接池:
|
|
|
+ * - 末尾请求的排队时间从 0.2s 涨到 2s 以上
|
|
|
+ * - 用户反复调整选品时,同一批物品会被重复查询
|
|
|
+ * 这里统一做三件事(与发货单 inventoryTable 的调度器保持一致):
|
|
|
+ * 1. 同键合并:相同查询条件的并发请求复用同一个 Promise,只打一次
|
|
|
+ * 2. 短时缓存:TTL 内直接复用上一次结果
|
|
|
+ * 3. 并发限流:全局最多 LOOKUP_MAX_CONCURRENCY 个请求同时在飞
|
|
|
+ * ==========================================================================
|
|
|
+ */
|
|
|
+ const LOOKUP_CACHE_TTL = 30 * 1000; // 商品信息缓存时长
|
|
|
+ const LOOKUP_MAX_CONCURRENCY = 6; // 同时在飞的请求上限
|
|
|
+
|
|
|
+ const lookupCache = new Map(); // key -> { expire, data }
|
|
|
+ const lookupPending = new Map(); // key -> Promise(同键合并)
|
|
|
+ const lookupQueue = []; // 等待队列
|
|
|
+ let lookupActive = 0; // 当前在飞的请求数
|
|
|
+
|
|
|
+ function pumpLookupQueue() {
|
|
|
+ while (lookupActive < LOOKUP_MAX_CONCURRENCY && lookupQueue.length) {
|
|
|
+ const task = lookupQueue.shift();
|
|
|
+ lookupActive++;
|
|
|
+ Promise.resolve()
|
|
|
+ .then(task.factory)
|
|
|
+ .then(
|
|
|
+ (data) => {
|
|
|
+ lookupActive--;
|
|
|
+ task.resolve(data);
|
|
|
+ pumpLookupQueue();
|
|
|
+ },
|
|
|
+ (error) => {
|
|
|
+ lookupActive--;
|
|
|
+ task.reject(error);
|
|
|
+ pumpLookupQueue();
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 走调度器的请求:命中缓存直接返回;相同 key 的并发请求合并成一次
|
|
|
+ * @param {string} key 查询键
|
|
|
+ * @param {Function} factory 真正发起请求的工厂函数
|
|
|
+ * @param {number} ttl 缓存时长,0 表示不缓存(只做同键合并 + 限流)
|
|
|
+ */
|
|
|
+ function scheduleLookup(key, factory, ttl = LOOKUP_CACHE_TTL) {
|
|
|
+ if (ttl) {
|
|
|
+ const cached = lookupCache.get(key);
|
|
|
+ if (cached) {
|
|
|
+ if (cached.expire > Date.now()) {
|
|
|
+ return Promise.resolve(cached.data);
|
|
|
+ }
|
|
|
+ lookupCache.delete(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const pending = lookupPending.get(key);
|
|
|
+ if (pending) {
|
|
|
+ return pending;
|
|
|
+ }
|
|
|
+ const promise = new Promise((resolve, reject) => {
|
|
|
+ lookupQueue.push({ factory, resolve, reject });
|
|
|
+ pumpLookupQueue();
|
|
|
+ });
|
|
|
+ lookupPending.set(key, promise);
|
|
|
+ promise.then(
|
|
|
+ (data) => {
|
|
|
+ lookupPending.delete(key);
|
|
|
+ if (ttl) {
|
|
|
+ lookupCache.set(key, { expire: Date.now() + ttl, data });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ lookupPending.delete(key);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ return promise;
|
|
|
+ }
|
|
|
+
|
|
|
export default {
|
|
|
mixins: [dictMixins, tabMixins],
|
|
|
components: {
|
|
|
@@ -1320,6 +1404,9 @@
|
|
|
tableLoading: false,
|
|
|
// 分批追加进度
|
|
|
batchLoading: false,
|
|
|
+ // 回显取数中(供应商/库存批量查询 + 分批入表):
|
|
|
+ // 这段时间表格内容还没完整,保存会被挡住,避免提交半成品数据
|
|
|
+ dataLoading: false,
|
|
|
loadedCount: 0,
|
|
|
totalCount: 0,
|
|
|
allPrice: 0,
|
|
|
@@ -1345,9 +1432,9 @@
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
- // 批量追加进行中:锁住会改动数据源的操作,避免行下标错位
|
|
|
+ // 批量追加 / 回显进行中:锁住会改动数据源的操作,避免行下标错位
|
|
|
tableBusy() {
|
|
|
- return this.batchLoading;
|
|
|
+ return this.batchLoading || this.dataLoading;
|
|
|
},
|
|
|
canHandl() {
|
|
|
return this.form.datasource.length;
|
|
|
@@ -2155,12 +2242,19 @@
|
|
|
modelType: item.modelType
|
|
|
};
|
|
|
},
|
|
|
- async refreshStockOrderInventory(validateQuantity = false) {
|
|
|
+ /**
|
|
|
+ * 刷新库存数量。
|
|
|
+ * rows 传入时按传入的数组分组与回填:修改回显时数据还在暂存数组里
|
|
|
+ * (还没进表格),直接用表格数据源会把结果写回旧数据。
|
|
|
+ * 不传则沿用表格当前数据源。
|
|
|
+ */
|
|
|
+ async refreshStockOrderInventory(validateQuantity = false, rows = null) {
|
|
|
if (this.needProduce != 0) {
|
|
|
return true;
|
|
|
}
|
|
|
+ const source = rows || this.form.datasource;
|
|
|
const inventoryGroups = new Map();
|
|
|
- this.form.datasource.forEach((item, index) => {
|
|
|
+ source.forEach((item, index) => {
|
|
|
if (!item.productCode || !item.warehouseId) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -2192,14 +2286,10 @@
|
|
|
const availableCountBase = Number(inventory.availableCountBase || 0);
|
|
|
group.rows.forEach((rowIndex) => {
|
|
|
if (this.showStockCount) {
|
|
|
- this.$set(
|
|
|
- this.form.datasource[rowIndex],
|
|
|
- 'stockCountBase',
|
|
|
- stockCountBase
|
|
|
- );
|
|
|
+ this.$set(source[rowIndex], 'stockCountBase', stockCountBase);
|
|
|
}
|
|
|
this.$set(
|
|
|
- this.form.datasource[rowIndex],
|
|
|
+ source[rowIndex],
|
|
|
'availableCountBase',
|
|
|
availableCountBase
|
|
|
);
|
|
|
@@ -2541,108 +2631,141 @@
|
|
|
(data &&
|
|
|
(data.quoteProductList || data.productList || data.detailList)) ||
|
|
|
[];
|
|
|
- if (productList) {
|
|
|
- let supplierObj = await this.getSupplierObj(productList, 'productId');
|
|
|
-
|
|
|
- productList.forEach((item, index) => {
|
|
|
- item['key'] = index + 1;
|
|
|
- if (item.modelKey) {
|
|
|
- item.modelKey = item.modelKey;
|
|
|
- }
|
|
|
- if (item.colorKey) {
|
|
|
- item.colorKey = item.colorKey;
|
|
|
- }
|
|
|
+ if (!productList) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 回显期间表格内容还不完整:锁住会改动数据源 / 提交的操作,
|
|
|
+ // 并让遮罩覆盖整个回显过程(批量取数 + 分批入表)。
|
|
|
+ this.dataLoading = true;
|
|
|
+ this._loadingStart = Date.now();
|
|
|
+ this.tableLoading = true;
|
|
|
+ try {
|
|
|
+ await this.putTableValueRows(productList, data);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('物品清单回显失败', error);
|
|
|
+ this.$message.error('物品清单加载失败,请重试');
|
|
|
+ } finally {
|
|
|
+ this.dataLoading = false;
|
|
|
+ this.endTableLoading();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 回显主体:准备行数据 -> 批量取库存 -> 分批入表。
|
|
|
+ * 拆成独立方法,方便 putTableValue 统一兜住 loading 状态与异常。
|
|
|
+ */
|
|
|
+ async putTableValueRows(productList, data) {
|
|
|
+ let supplierObj = await this.getSupplierObj(productList, 'productId');
|
|
|
|
|
|
- item.guaranteePeriodUnitCode = item.guaranteePeriodUnitCode
|
|
|
- ? item.guaranteePeriodUnitCode + ''
|
|
|
- : '';
|
|
|
+ productList.forEach((item, index) => {
|
|
|
+ item['key'] = index + 1;
|
|
|
+ if (item.modelKey) {
|
|
|
+ item.modelKey = item.modelKey;
|
|
|
+ }
|
|
|
+ if (item.colorKey) {
|
|
|
+ item.colorKey = item.colorKey;
|
|
|
+ }
|
|
|
|
|
|
- let guaranteePeriodUnitName = this.guaranteePeriodUnit(
|
|
|
- item.guaranteePeriodUnitCode
|
|
|
- );
|
|
|
- if (item.guaranteePeriod && item.guaranteePeriodUnitCode) {
|
|
|
- item['guaranteePeriodDeadline'] =
|
|
|
- item['guaranteePeriodDeadline'] ||
|
|
|
- guaranteePeriodUnitName != 'second'
|
|
|
- ? this.setDay(
|
|
|
- item.guaranteePeriod,
|
|
|
- guaranteePeriodUnitName,
|
|
|
- item
|
|
|
- )
|
|
|
- : '';
|
|
|
- }
|
|
|
- item.entrustedEnterpriseIdList = supplierObj[item.productId];
|
|
|
- // if (supplierObj[item.productId]?.length === 1) {
|
|
|
- // item.entrustedEnterpriseId = supplierObj[item.productId][0].id;
|
|
|
- // }
|
|
|
- // item.entrustedEnterpriseId = item.entrustedEnterpriseId || undefined;
|
|
|
- this.$set(
|
|
|
- item,
|
|
|
- 'entrustedEnterpriseId',
|
|
|
- item.entrustedEnterpriseId || ''
|
|
|
- );
|
|
|
- item['customerMark'] = item.customerMark || this.customerMark;
|
|
|
- item['arrivalWay'] = item.arrivalWay || 1;
|
|
|
- item.discountRatio = Number(item.discountRatio) || 100;
|
|
|
- item.taxRate =
|
|
|
- item.taxRate !== undefined && item.taxRate !== null
|
|
|
- ? item.taxRate
|
|
|
- : this.isContractBook
|
|
|
- ? this.defTaxRate
|
|
|
- : this.isTaxRate == 1
|
|
|
- ? this.defTaxRate
|
|
|
- : undefined;
|
|
|
- item.quoteWay = item.quoteWay || 1;
|
|
|
- });
|
|
|
- this.form.datasource = productList;
|
|
|
+ item.guaranteePeriodUnitCode = item.guaranteePeriodUnitCode
|
|
|
+ ? item.guaranteePeriodUnitCode + ''
|
|
|
+ : '';
|
|
|
|
|
|
- if (this.needProduce == 0) {
|
|
|
- await this.refreshStockOrderInventory();
|
|
|
- } else {
|
|
|
- let codeList = this.form.datasource
|
|
|
- .filter((item) => item.productCode)
|
|
|
- .map((item) => buildInventoryTotalQueryKey(item));
|
|
|
- // 非库存式业务保留原有物品/批次汇总库存口径。
|
|
|
- let inventoryTotalList = await getInventoryTotalAPI(codeList);
|
|
|
- this.form.datasource
|
|
|
- .filter((item) => item.productCode)
|
|
|
- .forEach((item, index) => {
|
|
|
- let find =
|
|
|
- inventoryTotalList.find((key) =>
|
|
|
- matchInventoryTotal(item, key)
|
|
|
- ) || {};
|
|
|
- this.form.datasource;
|
|
|
- if (this.showStockCount) {
|
|
|
- this.$set(
|
|
|
- this.form.datasource[index],
|
|
|
- 'stockCountBase',
|
|
|
- this.getStockCount(find)
|
|
|
- );
|
|
|
- }
|
|
|
+ let guaranteePeriodUnitName = this.guaranteePeriodUnit(
|
|
|
+ item.guaranteePeriodUnitCode
|
|
|
+ );
|
|
|
+ if (item.guaranteePeriod && item.guaranteePeriodUnitCode) {
|
|
|
+ item['guaranteePeriodDeadline'] =
|
|
|
+ item['guaranteePeriodDeadline'] ||
|
|
|
+ guaranteePeriodUnitName != 'second'
|
|
|
+ ? this.setDay(
|
|
|
+ item.guaranteePeriod,
|
|
|
+ guaranteePeriodUnitName,
|
|
|
+ item
|
|
|
+ )
|
|
|
+ : '';
|
|
|
+ }
|
|
|
+ item.entrustedEnterpriseIdList = supplierObj[item.productId];
|
|
|
+ // if (supplierObj[item.productId]?.length === 1) {
|
|
|
+ // item.entrustedEnterpriseId = supplierObj[item.productId][0].id;
|
|
|
+ // }
|
|
|
+ // item.entrustedEnterpriseId = item.entrustedEnterpriseId || undefined;
|
|
|
+ this.$set(
|
|
|
+ item,
|
|
|
+ 'entrustedEnterpriseId',
|
|
|
+ item.entrustedEnterpriseId || ''
|
|
|
+ );
|
|
|
+ item['customerMark'] = item.customerMark || this.customerMark;
|
|
|
+ item['arrivalWay'] = item.arrivalWay || 1;
|
|
|
+ item.discountRatio = Number(item.discountRatio) || 100;
|
|
|
+ item.taxRate =
|
|
|
+ item.taxRate !== undefined && item.taxRate !== null
|
|
|
+ ? item.taxRate
|
|
|
+ : this.isContractBook
|
|
|
+ ? this.defTaxRate
|
|
|
+ : this.isTaxRate == 1
|
|
|
+ ? this.defTaxRate
|
|
|
+ : undefined;
|
|
|
+ item.quoteWay = item.quoteWay || 1;
|
|
|
+ });
|
|
|
+ // 回显期间数据先放在暂存数组 productList 上处理,不要直接整表塞进表格:
|
|
|
+ // 上百行 × 数十个表单项一次性渲染会让主线程长时间阻塞,页面直接卡死。
|
|
|
+ if (this.needProduce == 0) {
|
|
|
+ await this.refreshStockOrderInventory(false, productList);
|
|
|
+ } else {
|
|
|
+ // 同一「物品编码+批次」只提交一次:订单有上百行时请求体不再重复膨胀,
|
|
|
+ // 回填仍按 matchInventoryTotal 按内容匹配,去重不影响匹配结果。
|
|
|
+ let codeList = [
|
|
|
+ ...new Set(
|
|
|
+ productList
|
|
|
+ .filter((item) => item.productCode)
|
|
|
+ .map((item) => buildInventoryTotalQueryKey(item))
|
|
|
+ )
|
|
|
+ ];
|
|
|
+ // 非库存式业务保留原有物品/批次汇总库存口径。
|
|
|
+ let inventoryTotalList = await getInventoryTotalAPI(codeList);
|
|
|
+ productList
|
|
|
+ .filter((item) => item.productCode)
|
|
|
+ .forEach((item, index) => {
|
|
|
+ let find =
|
|
|
+ inventoryTotalList.find((key) =>
|
|
|
+ matchInventoryTotal(item, key)
|
|
|
+ ) || {};
|
|
|
+ if (this.showStockCount) {
|
|
|
this.$set(
|
|
|
- this.form.datasource[index],
|
|
|
- 'availableCountBase',
|
|
|
- this.showStockCount
|
|
|
- ? this.getAvailableStockCount(find)
|
|
|
- : find.availableCountBase
|
|
|
+ productList[index],
|
|
|
+ 'stockCountBase',
|
|
|
+ this.getStockCount(find)
|
|
|
);
|
|
|
- });
|
|
|
- }
|
|
|
- if (this.isChangeCount) {
|
|
|
- this.changeCount();
|
|
|
- } else {
|
|
|
- this.allPrice = data.totalAmount || data?.contractVO?.totalPrice;
|
|
|
- this.allQuantity = getAllQuantity(
|
|
|
- this.form.datasource,
|
|
|
- this.countObj
|
|
|
- );
|
|
|
- }
|
|
|
+ }
|
|
|
+ this.$set(
|
|
|
+ productList[index],
|
|
|
+ 'availableCountBase',
|
|
|
+ this.showStockCount
|
|
|
+ ? this.getAvailableStockCount(find)
|
|
|
+ : find.availableCountBase
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- if (this.isDiscountTotalPrice) {
|
|
|
- this.form.discountTotalPrice =
|
|
|
- data.payAmount || data?.contractVO?.discountTotalPrice;
|
|
|
- }
|
|
|
- this.$refs.table.reload();
|
|
|
+ // 分批入表:每批 appendBatchSize 行,批与批之间让出一帧,
|
|
|
+ // 页面在整个回显过程中保持可响应(与发货单编辑回显的处理方式一致)。
|
|
|
+ this.form.datasource = [];
|
|
|
+ await this.appendRowsInBatches(productList, 0, null, {
|
|
|
+ recalculate: false
|
|
|
+ });
|
|
|
+
|
|
|
+ if (this.isChangeCount) {
|
|
|
+ this.changeCount();
|
|
|
+ } else {
|
|
|
+ this.allPrice = data.totalAmount || data?.contractVO?.totalPrice;
|
|
|
+ this.allQuantity = getAllQuantity(
|
|
|
+ this.form.datasource,
|
|
|
+ this.countObj
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.isDiscountTotalPrice) {
|
|
|
+ this.form.discountTotalPrice =
|
|
|
+ data.payAmount || data?.contractVO?.discountTotalPrice;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -2869,45 +2992,56 @@
|
|
|
* 分批追加:一次选中上百个物品时,一次性塞进表格会让主线程长时间阻塞,
|
|
|
* 这里按 appendBatchSize 分批写入并重算,每批之间让出一帧,
|
|
|
* 浏览器可以正常绘制 loading 遮罩和已经渲染出来的行。
|
|
|
+ *
|
|
|
+ * options.recalculate = false 时不重算本批行:
|
|
|
+ * 修改回显的是用户已经保存过的数据,不能按新增的逻辑重算价格/数量。
|
|
|
*/
|
|
|
appendRowsInBatches(
|
|
|
rows,
|
|
|
startIndex = this.form.datasource.length,
|
|
|
- onDone
|
|
|
+ onDone,
|
|
|
+ options = {}
|
|
|
) {
|
|
|
- if (!rows.length) {
|
|
|
- if (onDone) onDone();
|
|
|
- this.endTableLoading();
|
|
|
- return;
|
|
|
- }
|
|
|
- const batchSize = Math.max(1, this.appendBatchSize || 20);
|
|
|
- const total = rows.length;
|
|
|
- this.batchLoading = true;
|
|
|
- this.loadedCount = 0;
|
|
|
- this.totalCount = total;
|
|
|
- let done = 0;
|
|
|
-
|
|
|
- const step = () => {
|
|
|
- const batch = rows.slice(done, done + batchSize);
|
|
|
- if (batch.length) {
|
|
|
- const from = startIndex + done;
|
|
|
- this.form.datasource = this.form.datasource.concat(batch);
|
|
|
- // 只重算本批新增的行,其余行复用原对象
|
|
|
- this.recalculateTableOnce(batch.map((row, i) => from + i));
|
|
|
- done += batch.length;
|
|
|
- this.loadedCount = done;
|
|
|
- }
|
|
|
- if (done < total) {
|
|
|
- requestAnimationFrame(step);
|
|
|
+ const shouldRecalculate = options.recalculate !== false;
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ if (!rows.length) {
|
|
|
+ if (onDone) onDone();
|
|
|
+ this.endTableLoading();
|
|
|
+ resolve();
|
|
|
return;
|
|
|
}
|
|
|
- // 全部批次都写完并渲染完,才收尾关闭 loading
|
|
|
- this.batchLoading = false;
|
|
|
- if (onDone) onDone();
|
|
|
- this.endTableLoading();
|
|
|
- };
|
|
|
+ const batchSize = Math.max(1, this.appendBatchSize || 20);
|
|
|
+ const total = rows.length;
|
|
|
+ this.batchLoading = true;
|
|
|
+ this.loadedCount = 0;
|
|
|
+ this.totalCount = total;
|
|
|
+ let done = 0;
|
|
|
+
|
|
|
+ const step = () => {
|
|
|
+ const batch = rows.slice(done, done + batchSize);
|
|
|
+ if (batch.length) {
|
|
|
+ const from = startIndex + done;
|
|
|
+ this.form.datasource = this.form.datasource.concat(batch);
|
|
|
+ // 只重算本批新增的行,其余行复用原对象
|
|
|
+ if (shouldRecalculate) {
|
|
|
+ this.recalculateTableOnce(batch.map((row, i) => from + i));
|
|
|
+ }
|
|
|
+ done += batch.length;
|
|
|
+ this.loadedCount = done;
|
|
|
+ }
|
|
|
+ if (done < total) {
|
|
|
+ requestAnimationFrame(step);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 全部批次都写完并渲染完,才收尾关闭 loading
|
|
|
+ this.batchLoading = false;
|
|
|
+ if (onDone) onDone();
|
|
|
+ this.endTableLoading();
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
|
|
|
- requestAnimationFrame(step);
|
|
|
+ requestAnimationFrame(step);
|
|
|
+ });
|
|
|
},
|
|
|
async replaceTable(obj, idx) {
|
|
|
// 先按物品ID去重并并行查询商品信息,避免逐行串行请求,物品较多时明显加快回显
|
|
|
@@ -3090,8 +3224,11 @@
|
|
|
},
|
|
|
/**
|
|
|
* 批量查询物品对应的商品信息。
|
|
|
- * 按物品ID去重后并行请求,替代逐行 await 的串行查询,
|
|
|
- * 同一物品选中多行(多批次)时只请求一次。
|
|
|
+ * 按物品ID去重后走调度器并行请求,替代逐行 await 的串行查询:
|
|
|
+ * - 同一物品选中多行(多批次)时只请求一次
|
|
|
+ * - 相同物品ID的并发请求合并成一次
|
|
|
+ * - TTL 内重复选同一批物品直接命中缓存,不再打接口
|
|
|
+ * - 同时在飞的请求数受 LOOKUP_MAX_CONCURRENCY 限制,避免打满连接池
|
|
|
*/
|
|
|
async loadGoodsByCategoryIds(list) {
|
|
|
const goodsMap = new Map();
|
|
|
@@ -3102,7 +3239,10 @@
|
|
|
];
|
|
|
await Promise.all(
|
|
|
productIds.map(async (productId) => {
|
|
|
- const goodsList = await getGoodsByCategoryId(productId);
|
|
|
+ const goodsList = await scheduleLookup(
|
|
|
+ `goodsByCategoryId:${productId}`,
|
|
|
+ () => getGoodsByCategoryId(productId)
|
|
|
+ );
|
|
|
goodsMap.set(productId, goodsList || []);
|
|
|
})
|
|
|
);
|