@pisell/pisellos 2.3.73 → 2.3.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +111 -71
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/strategy-example.js +5 -19
- package/dist/modules/Cart/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +0 -1
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.js +2 -4
- package/dist/modules/OpenData/index.js +0 -2
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/modules/Order/index.js +36 -64
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/Quotation/index.d.ts +6 -0
- package/dist/modules/Quotation/index.js +75 -19
- package/dist/modules/Rules/index.js +1 -4
- package/dist/modules/Schedule/index.js +0 -1
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/plugins/request.d.ts +1 -0
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.d.ts +8 -2
- package/dist/server/index.js +303 -269
- package/dist/server/modules/floor-plan/index.d.ts +4 -0
- package/dist/server/modules/floor-plan/index.js +240 -98
- package/dist/server/modules/menu/index.js +75 -59
- package/dist/server/modules/order/index.d.ts +17 -7
- package/dist/server/modules/order/index.js +421 -211
- package/dist/server/modules/products/index.d.ts +8 -2
- package/dist/server/modules/products/index.js +191 -117
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +24 -17
- package/dist/server/modules/schedule/index.js +27 -35
- package/dist/server/utils/product.js +0 -1
- package/dist/solution/BaseSales/index.js +38 -41
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingByStep/index.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.js +0 -2
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
- package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.js +177 -252
- package/dist/solution/Checkout/utils/index.js +4 -16
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/ScanOrder/index.js +50 -60
- package/dist/solution/ShopDiscount/index.js +2 -7
- package/dist/solution/VenueBooking/index.js +45 -55
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.d.ts +7 -0
- package/lib/core/index.js +149 -131
- package/lib/effects/index.js +46 -57
- package/lib/index.js +82 -49
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
- package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
- package/lib/model/strategy/adapter/promotion/examples.js +222 -234
- package/lib/model/strategy/adapter/promotion/index.js +1 -0
- package/lib/model/strategy/adapter/promotion/type.js +243 -36
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.js +23 -42
- package/lib/modules/BookingContext/index.js +158 -136
- package/lib/modules/BookingContext/types.js +46 -32
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
- package/lib/modules/BookingContext/utils/flexible.js +38 -55
- package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
- package/lib/modules/BookingContext/utils/index.js +124 -41
- package/lib/modules/BookingContext/utils/noResource.js +58 -70
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
- package/lib/modules/BookingContext/utils/productExtend.js +202 -155
- package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
- package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
- package/lib/modules/BookingContext/utils/resources.js +209 -167
- package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
- package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
- package/lib/modules/Cart/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +321 -251
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +70 -78
- package/lib/modules/Cart/utils/changePrice.js +22 -50
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.js +235 -172
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.js +122 -127
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.js +70 -76
- package/lib/modules/OpenData/types.js +4 -16
- package/lib/modules/OpenData/utils.js +44 -78
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/modules/Order/index.js +1713 -2118
- package/lib/modules/Order/payment-utils.js +77 -120
- package/lib/modules/Order/types.js +88 -35
- package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
- package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
- package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
- package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
- package/lib/modules/Order/utils.js +814 -647
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.js +532 -399
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.js +230 -108
- package/lib/modules/Payment/utils.js +52 -51
- package/lib/modules/Payment/walletpass.js +485 -660
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.js +113 -123
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.d.ts +6 -0
- package/lib/modules/Quotation/index.js +110 -103
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- package/lib/modules/ResourcePlanner/index.js +25 -23
- package/lib/modules/ResourcePlanner/localClock.js +54 -119
- package/lib/modules/ResourcePlanner/planner.js +589 -770
- package/lib/modules/ResourcePlanner/types.js +10 -29
- package/lib/modules/Rules/index.js +1218 -959
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.js +85 -90
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.js +355 -430
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
- package/lib/modules/Schedule/index.js +100 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -71
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +773 -418
- package/lib/modules/SurchargeList/index.js +46 -60
- package/lib/modules/SurchargeList/types.js +4 -16
- package/lib/modules/index.js +245 -63
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.d.ts +1 -0
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.d.ts +8 -2
- package/lib/server/index.js +3083 -2663
- package/lib/server/modules/floor-plan/index.d.ts +4 -0
- package/lib/server/modules/floor-plan/index.js +159 -127
- package/lib/server/modules/floor-plan/types.js +15 -30
- package/lib/server/modules/index.js +106 -68
- package/lib/server/modules/menu/index.js +170 -124
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.d.ts +17 -7
- package/lib/server/modules/order/index.js +916 -977
- package/lib/server/modules/order/types.js +122 -33
- package/lib/server/modules/order/utils/filterBookings.js +219 -194
- package/lib/server/modules/order/utils/filterOrders.js +118 -92
- package/lib/server/modules/payment/index.js +59 -83
- package/lib/server/modules/payment/types.js +4 -16
- package/lib/server/modules/products/index.d.ts +8 -2
- package/lib/server/modules/products/index.js +656 -471
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.js +137 -172
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.js +225 -186
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.js +135 -123
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +196 -139
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.js +479 -456
- package/lib/server/utils/time.js +40 -49
- package/lib/solution/BaseSales/index.js +1186 -1410
- package/lib/solution/BaseSales/types.js +42 -38
- package/lib/solution/BaseSales/utils/cartPromotion.js +642 -498
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
- package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
- package/lib/solution/BaseSales/utils.js +158 -143
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingByStep/index.js +1527 -1340
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.js +607 -522
- package/lib/solution/BookingTicket/types.js +99 -77
- package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
- package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
- package/lib/solution/BookingTicket/utils/cartView.js +114 -99
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
- package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
- package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
- package/lib/solution/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.js +1451 -1158
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -156
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +493 -460
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.js +328 -324
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.js +832 -864
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.js +409 -374
- package/lib/solution/ShopDiscount/index.js +226 -232
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +300 -172
- package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
- package/lib/solution/UnifiedBookingSales/types.js +10 -32
- package/lib/solution/VenueBooking/index.js +811 -879
- package/lib/solution/VenueBooking/types.js +19 -39
- package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
- package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.js +124 -41
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.js +4 -16
- package/lib/utils/payment-number.js +26 -46
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
|
@@ -1,100 +1,117 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __export = (target, all) => {
|
|
6
|
-
for (var name in all)
|
|
7
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
-
};
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
-
|
|
19
|
-
// src/server/modules/products/index.ts
|
|
20
|
-
var products_exports = {};
|
|
21
|
-
__export(products_exports, {
|
|
22
|
-
ProductsModule: () => ProductsModule
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
23
5
|
});
|
|
24
|
-
|
|
25
|
-
var
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
6
|
+
exports.ProductsModule = void 0;
|
|
7
|
+
var _BaseModule = require("../../../modules/BaseModule");
|
|
8
|
+
var _Quotation = require("../../../modules/Quotation");
|
|
9
|
+
var _types = require("./types");
|
|
10
|
+
var _product = require("../../utils/product");
|
|
11
|
+
/**
|
|
12
|
+
* IndexDB 存储名称
|
|
13
|
+
*/
|
|
14
|
+
const INDEXDB_STORE_NAME = 'products';
|
|
15
|
+
/** 商品同步防抖时间(毫秒) */
|
|
16
|
+
const PRODUCT_SYNC_DEBOUNCE_MS = 10_000;
|
|
17
|
+
/** silentRefresh 最小间隔,避免 preload 后立即全量重拉 */
|
|
18
|
+
const PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS = 30_000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Products 模块 - 用于获取和管理完整的商品详细数据
|
|
22
|
+
* 相比 ProductList 模块,Products 会获取所有详细信息并缓存
|
|
23
|
+
*/
|
|
24
|
+
class ProductsModule extends _BaseModule.BaseModule {
|
|
25
|
+
defaultName = 'products';
|
|
26
|
+
defaultVersion = '1.0.0';
|
|
27
|
+
store;
|
|
28
|
+
request;
|
|
29
|
+
dbManager; // IndexDBManager 实例
|
|
30
|
+
logger; // LoggerManager 实例
|
|
31
|
+
otherParams = {};
|
|
32
|
+
|
|
33
|
+
// 商品价格缓存:Map<日期, 应用了价格的商品列表>
|
|
34
|
+
productsPriceCache = new Map();
|
|
35
|
+
|
|
36
|
+
// 缓存配置
|
|
37
|
+
CACHE_MAX_DAYS = 7; // 最多缓存7天
|
|
38
|
+
|
|
39
|
+
// 商品格式化器列表
|
|
40
|
+
formatters = [];
|
|
41
|
+
|
|
42
|
+
// 是否已注册内置商品格式化器
|
|
43
|
+
isBuiltinFormatterRegistered = false;
|
|
44
|
+
// 旧报价单格式化器默认关闭,仅显式开启时作为兼容兜底注册
|
|
45
|
+
isLegacyPriceFormatterEnabled = false;
|
|
46
|
+
|
|
47
|
+
// 普通层 QuotationModule 桥接器:本地计算报价单价格,替代 /product/query/price
|
|
48
|
+
quotationPriceBridge;
|
|
49
|
+
quotationBridgeChannel;
|
|
50
|
+
quotationBridgeLoadedKey = '';
|
|
51
|
+
quotationBridgeRefreshPromise;
|
|
52
|
+
quotationBridgeRefreshKey;
|
|
53
|
+
quotationBridgeUpdatedCallback;
|
|
54
|
+
quotationScheduleCache = new Map();
|
|
55
|
+
quotationScheduleCacheSource = null;
|
|
56
|
+
quotationScheduleCacheLoaded = false;
|
|
57
|
+
|
|
58
|
+
// ---- 商品数据同步相关 ----
|
|
59
|
+
productDataSource;
|
|
60
|
+
pendingSyncMessages = [];
|
|
61
|
+
syncTimer;
|
|
62
|
+
/** 最近一次 SSE 全量拉取完成时间 */
|
|
63
|
+
lastServerFullFetchAtMs = 0;
|
|
64
|
+
/** 最近一次 preload 完成时间(含 IndexDB 命中) */
|
|
65
|
+
lastPreloadCompletedAtMs = 0;
|
|
66
|
+
/** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
|
|
67
|
+
productIndexDBSaveQueue = Promise.resolve();
|
|
68
|
+
|
|
69
|
+
/** 最近一次智能定价评估提取的 schedule 变价时间点(HH:mm),供 Server 订阅定时刷新使用 */
|
|
70
|
+
lastScheduleTimePoints = [];
|
|
33
71
|
constructor(name, version) {
|
|
34
72
|
super(name, version);
|
|
35
|
-
this.defaultName = "products";
|
|
36
|
-
this.defaultVersion = "1.0.0";
|
|
37
|
-
// LoggerManager 实例
|
|
38
|
-
this.otherParams = {};
|
|
39
|
-
// 商品价格缓存:Map<日期, 应用了价格的商品列表>
|
|
40
|
-
this.productsPriceCache = /* @__PURE__ */ new Map();
|
|
41
|
-
// 缓存配置
|
|
42
|
-
this.CACHE_MAX_DAYS = 7;
|
|
43
|
-
// 最多缓存7天
|
|
44
|
-
// 商品格式化器列表
|
|
45
|
-
this.formatters = [];
|
|
46
|
-
// 是否已注册内置商品格式化器
|
|
47
|
-
this.isBuiltinFormatterRegistered = false;
|
|
48
|
-
// 旧报价单格式化器默认关闭,仅显式开启时作为兼容兜底注册
|
|
49
|
-
this.isLegacyPriceFormatterEnabled = false;
|
|
50
|
-
this.quotationBridgeLoadedKey = "";
|
|
51
|
-
this.quotationScheduleCache = /* @__PURE__ */ new Map();
|
|
52
|
-
this.quotationScheduleCacheSource = null;
|
|
53
|
-
this.quotationScheduleCacheLoaded = false;
|
|
54
|
-
this.pendingSyncMessages = [];
|
|
55
|
-
/** 最近一次 SSE 全量拉取完成时间 */
|
|
56
|
-
this.lastServerFullFetchAtMs = 0;
|
|
57
|
-
/** 最近一次 preload 完成时间(含 IndexDB 命中) */
|
|
58
|
-
this.lastPreloadCompletedAtMs = 0;
|
|
59
|
-
/** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
|
|
60
|
-
this.productIndexDBSaveQueue = Promise.resolve();
|
|
61
|
-
/** 最近一次智能定价评估提取的 schedule 变价时间点(HH:mm),供 Server 订阅定时刷新使用 */
|
|
62
|
-
this.lastScheduleTimePoints = [];
|
|
63
73
|
}
|
|
64
74
|
async initialize(core, options) {
|
|
65
|
-
var _a;
|
|
66
75
|
this.core = core;
|
|
67
76
|
this.store = options.store;
|
|
68
77
|
this.otherParams = options.otherParams || {};
|
|
69
|
-
if (Array.isArray(
|
|
78
|
+
if (Array.isArray(options.initialState?.list)) {
|
|
70
79
|
this.store.list = options.initialState.list;
|
|
80
|
+
// 同步更新 Map 缓存
|
|
71
81
|
this.syncProductsMap();
|
|
72
|
-
this.core.effects.emit(
|
|
82
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
73
83
|
} else {
|
|
74
84
|
this.store.list = [];
|
|
75
|
-
this.store.map =
|
|
85
|
+
this.store.map = new Map();
|
|
76
86
|
}
|
|
77
|
-
this.request = core.getPlugin(
|
|
78
|
-
|
|
87
|
+
this.request = core.getPlugin('request');
|
|
88
|
+
|
|
89
|
+
// 获取并存储 dbManager 和 logger
|
|
90
|
+
const appPlugin = core.getPlugin('app');
|
|
79
91
|
if (appPlugin) {
|
|
80
92
|
const app = appPlugin.getApp();
|
|
81
93
|
this.dbManager = app.dbManager;
|
|
82
94
|
this.logger = app.logger;
|
|
83
|
-
if (this.dbManager) {
|
|
84
|
-
console.
|
|
85
|
-
} else {
|
|
86
|
-
console.warn("[Products] IndexDB Manager 未找到");
|
|
95
|
+
if (this.dbManager) {} else {
|
|
96
|
+
console.warn('[Products] IndexDB Manager 未找到');
|
|
87
97
|
}
|
|
88
98
|
}
|
|
99
|
+
|
|
100
|
+
// 注册内置商品格式化器;智能定价默认替代报价单价格逻辑
|
|
89
101
|
this.registerBuiltinProductFormatters();
|
|
102
|
+
|
|
103
|
+
// 初始化 ProductDataSource 实例
|
|
90
104
|
this.initProductDataSource();
|
|
105
|
+
|
|
106
|
+
// 初始化商品数据同步(pubsub 订阅)
|
|
91
107
|
this.setupProductSync();
|
|
92
|
-
this.logInfo(
|
|
108
|
+
this.logInfo('模块初始化完成', {
|
|
93
109
|
hasDbManager: !!this.dbManager,
|
|
94
110
|
hasLogger: !!this.logger,
|
|
95
111
|
initialProductCount: this.store.list.length
|
|
96
112
|
});
|
|
97
113
|
}
|
|
114
|
+
|
|
98
115
|
/**
|
|
99
116
|
* 记录信息日志
|
|
100
117
|
* @param title 日志标题
|
|
@@ -104,14 +121,16 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
104
121
|
try {
|
|
105
122
|
if (this.logger) {
|
|
106
123
|
this.logger.addLog({
|
|
107
|
-
type:
|
|
124
|
+
type: 'info',
|
|
108
125
|
title: `[ProductsModule] ${title}`,
|
|
109
126
|
metadata: metadata || {}
|
|
110
127
|
});
|
|
111
128
|
}
|
|
112
129
|
} catch {
|
|
130
|
+
// 日志记录失败不影响主流程
|
|
113
131
|
}
|
|
114
132
|
}
|
|
133
|
+
|
|
115
134
|
/**
|
|
116
135
|
* 记录警告日志
|
|
117
136
|
* @param title 日志标题
|
|
@@ -121,14 +140,16 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
121
140
|
try {
|
|
122
141
|
if (this.logger) {
|
|
123
142
|
this.logger.addLog({
|
|
124
|
-
type:
|
|
143
|
+
type: 'warning',
|
|
125
144
|
title: `[ProductsModule] ${title}`,
|
|
126
145
|
metadata: metadata || {}
|
|
127
146
|
});
|
|
128
147
|
}
|
|
129
148
|
} catch {
|
|
149
|
+
// 日志记录失败不影响主流程
|
|
130
150
|
}
|
|
131
151
|
}
|
|
152
|
+
|
|
132
153
|
/**
|
|
133
154
|
* 记录错误日志
|
|
134
155
|
* @param title 日志标题
|
|
@@ -138,14 +159,16 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
138
159
|
try {
|
|
139
160
|
if (this.logger) {
|
|
140
161
|
this.logger.addLog({
|
|
141
|
-
type:
|
|
162
|
+
type: 'error',
|
|
142
163
|
title: `[ProductsModule] ${title}`,
|
|
143
164
|
metadata: metadata || {}
|
|
144
165
|
});
|
|
145
166
|
}
|
|
146
167
|
} catch {
|
|
168
|
+
// 日志记录失败不影响主流程
|
|
147
169
|
}
|
|
148
170
|
}
|
|
171
|
+
|
|
149
172
|
/**
|
|
150
173
|
* 加载商品价格(原始方法,不带缓存)
|
|
151
174
|
* @private
|
|
@@ -156,7 +179,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
156
179
|
schedule_datetime,
|
|
157
180
|
customer_id
|
|
158
181
|
}) {
|
|
159
|
-
this.logInfo(
|
|
182
|
+
this.logInfo('开始加载商品价格', {
|
|
160
183
|
productCount: ids.length,
|
|
161
184
|
schedule_date,
|
|
162
185
|
schedule_datetime,
|
|
@@ -167,10 +190,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
167
190
|
try {
|
|
168
191
|
await this.ensureQuotationBridgeReady(customer_id);
|
|
169
192
|
const idSet = new Set(ids);
|
|
170
|
-
const products = this.getProductsRef().filter(
|
|
171
|
-
const priceData = products.map(
|
|
193
|
+
const products = this.getProductsRef().filter(product => idSet.has(product.id));
|
|
194
|
+
const priceData = products.map(product => this.buildPriceDataFromQuotation(product, datetime, customer_id));
|
|
172
195
|
const duration = Date.now() - startTime;
|
|
173
|
-
this.logInfo(
|
|
196
|
+
this.logInfo('加载商品价格成功', {
|
|
174
197
|
productCount: ids.length,
|
|
175
198
|
priceDataCount: priceData.length,
|
|
176
199
|
duration: `${duration}ms`
|
|
@@ -179,7 +202,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
179
202
|
} catch (error) {
|
|
180
203
|
const duration = Date.now() - startTime;
|
|
181
204
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
182
|
-
this.logError(
|
|
205
|
+
this.logError('加载商品价格失败', {
|
|
183
206
|
productCount: ids.length,
|
|
184
207
|
duration: `${duration}ms`,
|
|
185
208
|
error: errorMessage
|
|
@@ -187,20 +210,20 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
187
210
|
return [];
|
|
188
211
|
}
|
|
189
212
|
}
|
|
213
|
+
|
|
190
214
|
/**
|
|
191
215
|
* 设置报价单价格桥接器。
|
|
192
216
|
* Server 层负责传入完整 schedule 模块,Products 只负责把报价单计算结果转成价格响应形状。
|
|
193
217
|
*/
|
|
194
218
|
async setupQuotationPriceBridge(params) {
|
|
195
|
-
var _a;
|
|
196
219
|
if (!this.core) {
|
|
197
|
-
this.logWarning(
|
|
220
|
+
this.logWarning('setupQuotationPriceBridge: core 尚未初始化');
|
|
198
221
|
return;
|
|
199
222
|
}
|
|
200
|
-
const channel = params.channel ||
|
|
223
|
+
const channel = params.channel || this.otherParams?.channel;
|
|
201
224
|
this.quotationBridgeChannel = channel;
|
|
202
225
|
if (!this.quotationPriceBridge) {
|
|
203
|
-
const quotation = new
|
|
226
|
+
const quotation = new _Quotation.QuotationModule(`${this.name}_quotation_price_bridge`);
|
|
204
227
|
await quotation.initialize(this.core, {});
|
|
205
228
|
this.quotationPriceBridge = quotation;
|
|
206
229
|
}
|
|
@@ -210,14 +233,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
210
233
|
this.quotationScheduleCacheLoaded = false;
|
|
211
234
|
this.quotationScheduleCacheSource = params.scheduleModule;
|
|
212
235
|
}
|
|
213
|
-
this.quotationPriceBridge.setScheduleResolver(
|
|
214
|
-
var _a2, _b, _c, _d, _e, _f;
|
|
236
|
+
this.quotationPriceBridge.setScheduleResolver(id => {
|
|
215
237
|
const scheduleId = String(id);
|
|
216
238
|
if (this.quotationScheduleCache.has(scheduleId)) {
|
|
217
239
|
return this.quotationScheduleCache.get(scheduleId);
|
|
218
240
|
}
|
|
219
241
|
if (!this.quotationScheduleCacheLoaded) {
|
|
220
|
-
const scheduleList =
|
|
242
|
+
const scheduleList = params.scheduleModule.getScheduleList?.() || [];
|
|
221
243
|
for (const schedule of scheduleList) {
|
|
222
244
|
this.quotationScheduleCache.set(String(schedule.id), schedule);
|
|
223
245
|
}
|
|
@@ -226,20 +248,29 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
226
248
|
if (this.quotationScheduleCache.has(scheduleId)) {
|
|
227
249
|
return this.quotationScheduleCache.get(scheduleId);
|
|
228
250
|
}
|
|
229
|
-
const schedules =
|
|
230
|
-
if (schedules[0])
|
|
231
|
-
this.quotationScheduleCache.set(scheduleId, schedules[0]);
|
|
251
|
+
const schedules = params.scheduleModule.getScheduleListByIds?.([id]) || params.scheduleModule.getScheduleByIds?.([id]) || [];
|
|
252
|
+
if (schedules[0]) this.quotationScheduleCache.set(scheduleId, schedules[0]);
|
|
232
253
|
return schedules[0];
|
|
233
254
|
});
|
|
234
255
|
}
|
|
235
|
-
|
|
256
|
+
if (params.onQuotationUpdated) {
|
|
257
|
+
this.quotationBridgeUpdatedCallback = params.onQuotationUpdated;
|
|
258
|
+
}
|
|
259
|
+
this.quotationPriceBridge.setQuotationListUpdatedListener?.(() => {
|
|
260
|
+
this.quotationBridgeLoadedKey = this.getQuotationBridgeScopeKey();
|
|
261
|
+
this.clearPriceCache();
|
|
262
|
+
this.quotationBridgeUpdatedCallback?.();
|
|
263
|
+
});
|
|
264
|
+
await this.refreshQuotationPriceBridge({
|
|
265
|
+
customer_id: params.customer_id
|
|
266
|
+
});
|
|
236
267
|
}
|
|
268
|
+
|
|
237
269
|
/**
|
|
238
270
|
* 刷新桥接器内的报价单列表。
|
|
239
271
|
*/
|
|
240
272
|
async refreshQuotationPriceBridge(params) {
|
|
241
|
-
if (!this.quotationPriceBridge)
|
|
242
|
-
return;
|
|
273
|
+
if (!this.quotationPriceBridge) return;
|
|
243
274
|
const refreshKey = this.getQuotationBridgeScopeKey();
|
|
244
275
|
if (this.quotationBridgeRefreshPromise && this.quotationBridgeRefreshKey === refreshKey) {
|
|
245
276
|
await this.quotationBridgeRefreshPromise;
|
|
@@ -254,16 +285,15 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
254
285
|
}).then(() => {
|
|
255
286
|
this.quotationBridgeLoadedKey = refreshKey;
|
|
256
287
|
}).finally(() => {
|
|
257
|
-
this.quotationBridgeRefreshPromise =
|
|
258
|
-
this.quotationBridgeRefreshKey =
|
|
288
|
+
this.quotationBridgeRefreshPromise = undefined;
|
|
289
|
+
this.quotationBridgeRefreshKey = undefined;
|
|
259
290
|
});
|
|
260
291
|
await this.quotationBridgeRefreshPromise;
|
|
261
292
|
}
|
|
262
293
|
async ensureQuotationBridgeReady(customerId) {
|
|
263
|
-
var _a;
|
|
264
294
|
if (!this.quotationPriceBridge) {
|
|
265
295
|
await this.setupQuotationPriceBridge({
|
|
266
|
-
channel:
|
|
296
|
+
channel: this.otherParams?.channel,
|
|
267
297
|
customer_id: customerId
|
|
268
298
|
});
|
|
269
299
|
return;
|
|
@@ -272,27 +302,27 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
272
302
|
await this.quotationBridgeRefreshPromise;
|
|
273
303
|
}
|
|
274
304
|
if (this.getQuotationBridgeScopeKey() !== this.quotationBridgeLoadedKey) {
|
|
275
|
-
await this.refreshQuotationPriceBridge({
|
|
305
|
+
await this.refreshQuotationPriceBridge({
|
|
306
|
+
customer_id: customerId
|
|
307
|
+
});
|
|
276
308
|
}
|
|
277
309
|
}
|
|
278
310
|
getQuotationBridgeScopeKey() {
|
|
279
|
-
return this.quotationBridgeChannel ||
|
|
311
|
+
return this.quotationBridgeChannel || '';
|
|
280
312
|
}
|
|
281
313
|
getQuotedPrice(params) {
|
|
282
|
-
var _a;
|
|
283
314
|
const quotation = this.quotationPriceBridge;
|
|
284
|
-
if (!quotation)
|
|
285
|
-
return null;
|
|
315
|
+
if (!quotation) return null;
|
|
286
316
|
const query = {
|
|
287
317
|
productId: params.productId,
|
|
288
318
|
variantId: params.variantId,
|
|
289
319
|
datetime: params.datetime,
|
|
290
320
|
customer_id: params.customerId
|
|
291
321
|
};
|
|
292
|
-
if (typeof quotation.getQuotationMatchForProduct ===
|
|
293
|
-
return
|
|
322
|
+
if (typeof quotation.getQuotationMatchForProduct === 'function') {
|
|
323
|
+
return quotation.getQuotationMatchForProduct(query)?.price ?? null;
|
|
294
324
|
}
|
|
295
|
-
if (typeof quotation.getPriceForProduct ===
|
|
325
|
+
if (typeof quotation.getPriceForProduct === 'function') {
|
|
296
326
|
return quotation.getPriceForProduct(query);
|
|
297
327
|
}
|
|
298
328
|
return null;
|
|
@@ -314,9 +344,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
314
344
|
};
|
|
315
345
|
}
|
|
316
346
|
buildVariantPriceData(product, datetime, customerId) {
|
|
317
|
-
if (!Array.isArray(product.variant))
|
|
318
|
-
|
|
319
|
-
return product.variant.map((variant) => {
|
|
347
|
+
if (!Array.isArray(product.variant)) return [];
|
|
348
|
+
return product.variant.map(variant => {
|
|
320
349
|
const quotedPrice = this.getQuotedPrice({
|
|
321
350
|
productId: product.id,
|
|
322
351
|
variantId: Number(variant.id),
|
|
@@ -334,11 +363,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
334
363
|
});
|
|
335
364
|
}
|
|
336
365
|
buildBundleGroupPriceData(product, datetime, customerId) {
|
|
337
|
-
if (!Array.isArray(product.bundle_group))
|
|
338
|
-
|
|
339
|
-
return product.bundle_group.map((group) => ({
|
|
366
|
+
if (!Array.isArray(product.bundle_group)) return [];
|
|
367
|
+
return product.bundle_group.map(group => ({
|
|
340
368
|
id: Number(group.id),
|
|
341
|
-
bundle_item: (group.bundle_item || []).map(
|
|
369
|
+
bundle_item: (group.bundle_item || []).map(item => {
|
|
342
370
|
const bundleProductId = Number(item.bundle_product_id);
|
|
343
371
|
const bundleVariantId = Number(item.bundle_variant_id || 0);
|
|
344
372
|
const quotedPrice = this.getQuotedPrice({
|
|
@@ -363,6 +391,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
363
391
|
})
|
|
364
392
|
}));
|
|
365
393
|
}
|
|
394
|
+
|
|
366
395
|
/**
|
|
367
396
|
* 获取应用了价格的商品列表(带缓存)
|
|
368
397
|
* 使用日期作为缓存 key,同一天的商品价格会被缓存
|
|
@@ -375,28 +404,28 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
375
404
|
* @returns 应用了价格的商品列表
|
|
376
405
|
*/
|
|
377
406
|
async getProductsWithPrice(schedule_date, extraContext, options) {
|
|
378
|
-
var _a;
|
|
379
407
|
const t0 = performance.now();
|
|
380
|
-
const scopedProductIds = this.normalizeProductIds(options
|
|
408
|
+
const scopedProductIds = this.normalizeProductIds(options?.productIds);
|
|
381
409
|
const cacheKey = this.getProductsPriceCacheKey(schedule_date, extraContext, scopedProductIds);
|
|
382
|
-
const scopedProductIdSet = scopedProductIds ? new Set(scopedProductIds) :
|
|
383
|
-
const changedIds = scopedProductIdSet ?
|
|
410
|
+
const scopedProductIdSet = scopedProductIds ? new Set(scopedProductIds) : undefined;
|
|
411
|
+
const changedIds = scopedProductIdSet ? options?.changedIds?.filter(id => scopedProductIdSet.has(id)) : options?.changedIds;
|
|
384
412
|
if (this.productsPriceCache.has(cacheKey)) {
|
|
385
413
|
const cachedProducts = this.productsPriceCache.get(cacheKey);
|
|
414
|
+
|
|
415
|
+
// 有变更的商品 → 增量 prepare 后 merge 回缓存
|
|
386
416
|
if (changedIds && changedIds.length > 0) {
|
|
387
|
-
this.logInfo(
|
|
417
|
+
this.logInfo('商品价格缓存命中,增量更新变更商品', {
|
|
388
418
|
cacheKey,
|
|
389
419
|
changedIds,
|
|
390
420
|
cachedProductCount: cachedProducts.length
|
|
391
421
|
});
|
|
392
422
|
try {
|
|
393
|
-
const updatedProducts = await this.prepareProductsWithPrice(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
{ productIds: changedIds }
|
|
397
|
-
);
|
|
423
|
+
const updatedProducts = await this.prepareProductsWithPrice(schedule_date, extraContext, {
|
|
424
|
+
productIds: changedIds
|
|
425
|
+
});
|
|
398
426
|
if (updatedProducts.length > 0) {
|
|
399
|
-
|
|
427
|
+
// merge:用更新结果替换缓存中对应的商品,新增的追加
|
|
428
|
+
const updatedMap = new Map(updatedProducts.map(p => [p.id, p]));
|
|
400
429
|
const mergedCache = [];
|
|
401
430
|
for (const p of cachedProducts) {
|
|
402
431
|
if (updatedMap.has(p.id)) {
|
|
@@ -406,16 +435,17 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
406
435
|
mergedCache.push(p);
|
|
407
436
|
}
|
|
408
437
|
}
|
|
438
|
+
// 剩余的是新增商品(create 场景)
|
|
409
439
|
for (const p of updatedMap.values()) {
|
|
410
440
|
mergedCache.push(p);
|
|
411
441
|
}
|
|
412
442
|
this.productsPriceCache.set(cacheKey, mergedCache);
|
|
413
|
-
this.logInfo(
|
|
443
|
+
this.logInfo('增量更新完成', {
|
|
414
444
|
cacheKey,
|
|
415
445
|
changedCount: updatedProducts.length,
|
|
416
446
|
totalCount: mergedCache.length
|
|
417
447
|
});
|
|
418
|
-
(0,
|
|
448
|
+
(0, _product.perfMark)('getProductsWithPrice(incrementalUpdate)', performance.now() - t0, {
|
|
419
449
|
cacheKey,
|
|
420
450
|
changedCount: changedIds.length,
|
|
421
451
|
count: mergedCache.length
|
|
@@ -424,50 +454,49 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
424
454
|
}
|
|
425
455
|
} catch (error) {
|
|
426
456
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
427
|
-
this.logError(
|
|
457
|
+
this.logError('增量更新失败,返回现有缓存', {
|
|
428
458
|
cacheKey,
|
|
429
459
|
changedIds,
|
|
430
460
|
error: errorMessage
|
|
431
461
|
});
|
|
432
462
|
}
|
|
433
|
-
(0,
|
|
463
|
+
(0, _product.perfMark)('getProductsWithPrice(incrementalUpdate)', performance.now() - t0, {
|
|
434
464
|
cacheKey,
|
|
435
465
|
changedCount: changedIds.length,
|
|
436
466
|
count: cachedProducts.length
|
|
437
467
|
});
|
|
438
468
|
return cachedProducts;
|
|
439
469
|
}
|
|
440
|
-
(0,
|
|
470
|
+
(0, _product.perfMark)('getProductsWithPrice(cacheHit)', performance.now() - t0, {
|
|
441
471
|
cacheKey,
|
|
442
472
|
count: cachedProducts.length
|
|
443
473
|
});
|
|
444
|
-
this.logInfo(
|
|
474
|
+
this.logInfo('商品价格缓存命中', {
|
|
445
475
|
cacheKey,
|
|
446
476
|
productCount: cachedProducts.length
|
|
447
477
|
});
|
|
448
478
|
return cachedProducts;
|
|
449
479
|
}
|
|
450
|
-
this.logInfo(
|
|
480
|
+
this.logInfo('商品价格缓存未命中,准备获取', {
|
|
451
481
|
cacheKey,
|
|
452
|
-
scopedProductCount: scopedProductIds
|
|
482
|
+
scopedProductCount: scopedProductIds?.length
|
|
453
483
|
});
|
|
454
|
-
const result = await this.prepareProductsWithPrice(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
scopedProductIds ? { productIds: scopedProductIds } : void 0
|
|
458
|
-
);
|
|
484
|
+
const result = await this.prepareProductsWithPrice(schedule_date, extraContext, scopedProductIds ? {
|
|
485
|
+
productIds: scopedProductIds
|
|
486
|
+
} : undefined);
|
|
459
487
|
this.productsPriceCache.set(cacheKey, result);
|
|
460
|
-
this.logInfo(
|
|
488
|
+
this.logInfo('商品价格已缓存', {
|
|
461
489
|
cacheKey,
|
|
462
490
|
productCount: result.length
|
|
463
491
|
});
|
|
464
492
|
this.cleanExpiredPriceCache();
|
|
465
|
-
(0,
|
|
493
|
+
(0, _product.perfMark)('getProductsWithPrice(cacheMiss)', performance.now() - t0, {
|
|
466
494
|
cacheKey,
|
|
467
495
|
count: result.length
|
|
468
496
|
});
|
|
469
497
|
return result;
|
|
470
498
|
}
|
|
499
|
+
|
|
471
500
|
/**
|
|
472
501
|
* 准备带价格的商品数据(通过格式化器流程处理)
|
|
473
502
|
* @param schedule_date 日期
|
|
@@ -478,21 +507,21 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
478
507
|
* @private
|
|
479
508
|
*/
|
|
480
509
|
async prepareProductsWithPrice(schedule_date, extraContext, options) {
|
|
481
|
-
var _a, _b, _c, _d;
|
|
482
510
|
const tTotal = performance.now();
|
|
483
|
-
const targetIds = options
|
|
511
|
+
const targetIds = options?.productIds;
|
|
484
512
|
const isIncremental = Array.isArray(targetIds);
|
|
485
|
-
this.logInfo(
|
|
513
|
+
this.logInfo('prepareProductsWithPrice 开始处理', {
|
|
486
514
|
schedule_date,
|
|
487
|
-
mode: isIncremental ?
|
|
488
|
-
targetIdsCount: targetIds
|
|
515
|
+
mode: isIncremental ? 'incremental' : 'full',
|
|
516
|
+
targetIdsCount: targetIds?.length
|
|
489
517
|
});
|
|
490
518
|
try {
|
|
519
|
+
// 1. 获取商品列表:指定 IDs 时从 store 取子集,否则取全量
|
|
491
520
|
let products;
|
|
492
521
|
let ids;
|
|
493
522
|
if (isIncremental) {
|
|
494
523
|
products = this.getProductsRefByIds(targetIds);
|
|
495
|
-
ids = products.map(
|
|
524
|
+
ids = products.map(p => p.id);
|
|
496
525
|
} else {
|
|
497
526
|
products = this.getProductsRef();
|
|
498
527
|
const tIds = performance.now();
|
|
@@ -500,61 +529,69 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
500
529
|
for (let i = 0; i < products.length; i++) {
|
|
501
530
|
ids[i] = products[i].id;
|
|
502
531
|
}
|
|
503
|
-
(0,
|
|
532
|
+
(0, _product.perfMark)('prepareProducts.extractIds', performance.now() - tIds, {
|
|
533
|
+
count: ids.length
|
|
534
|
+
});
|
|
504
535
|
}
|
|
505
|
-
this.logInfo(
|
|
536
|
+
this.logInfo('获取到商品列表', {
|
|
537
|
+
productCount: products.length
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// 2. 仅在 legacy 报价单 formatter 启用时加载报价单价格;默认由智能定价覆盖商品价格。
|
|
506
541
|
let priceData = [];
|
|
507
542
|
if (this.isLegacyPriceFormatterEnabled) {
|
|
508
543
|
const tPrice = performance.now();
|
|
509
544
|
priceData = await this.loadProductsPrice({
|
|
510
545
|
ids,
|
|
511
546
|
schedule_date,
|
|
512
|
-
schedule_datetime: extraContext
|
|
513
|
-
customer_id: extraContext
|
|
547
|
+
schedule_datetime: extraContext?.schedule_datetime,
|
|
548
|
+
customer_id: extraContext?.customer_id
|
|
549
|
+
});
|
|
550
|
+
(0, _product.perfMark)('prepareProducts.loadPrice', performance.now() - tPrice, {
|
|
551
|
+
count: ids.length
|
|
552
|
+
});
|
|
553
|
+
this.logInfo('获取商品报价单价格成功', {
|
|
554
|
+
priceDataCount: priceData?.length ?? 0
|
|
514
555
|
});
|
|
515
|
-
(0, import_product.perfMark)("prepareProducts.loadPrice", performance.now() - tPrice, { count: ids.length });
|
|
516
|
-
this.logInfo("获取商品报价单价格成功", { priceDataCount: (priceData == null ? void 0 : priceData.length) ?? 0 });
|
|
517
556
|
}
|
|
518
557
|
const context = {
|
|
519
558
|
schedule_date,
|
|
520
|
-
schedule_datetime: extraContext
|
|
521
|
-
customer_id: extraContext
|
|
559
|
+
schedule_datetime: extraContext?.schedule_datetime,
|
|
560
|
+
customer_id: extraContext?.customer_id,
|
|
522
561
|
priceData,
|
|
523
|
-
locale:
|
|
524
|
-
dataVariantEvaluator:
|
|
525
|
-
menuList:
|
|
526
|
-
scheduleList:
|
|
527
|
-
strategy_context: extraContext
|
|
562
|
+
locale: this.core?.context?.locale,
|
|
563
|
+
dataVariantEvaluator: extraContext?.dataVariantEvaluator || this.core?.context?.dataVariantEvaluator,
|
|
564
|
+
menuList: extraContext?.menuList || [],
|
|
565
|
+
scheduleList: extraContext?.scheduleList || [],
|
|
566
|
+
strategy_context: extraContext?.strategy_context,
|
|
528
567
|
...extraContext
|
|
529
568
|
};
|
|
569
|
+
|
|
570
|
+
// 3. 通过格式化器流程处理商品
|
|
530
571
|
const tFormat = performance.now();
|
|
531
572
|
const processedProducts = await this.applyFormatters(products, context);
|
|
532
|
-
(0,
|
|
573
|
+
(0, _product.perfMark)('prepareProducts.applyFormatters', performance.now() - tFormat, {
|
|
533
574
|
count: products.length,
|
|
534
575
|
formatterCount: this.formatters.length
|
|
535
576
|
});
|
|
536
|
-
this.logInfo(
|
|
537
|
-
mode: isIncremental ?
|
|
577
|
+
this.logInfo('prepareProductsWithPrice 处理完成', {
|
|
578
|
+
mode: isIncremental ? 'incremental' : 'full',
|
|
538
579
|
originalProductCount: products.length,
|
|
539
580
|
processedProductCount: processedProducts.length,
|
|
540
581
|
formatterCount: this.formatters.length
|
|
541
582
|
});
|
|
542
583
|
if (!isIncremental) {
|
|
543
|
-
await this.core.effects.emit(
|
|
544
|
-
import_types.ProductsHooks.onProductsPriceApplied,
|
|
545
|
-
processedProducts
|
|
546
|
-
);
|
|
584
|
+
await this.core.effects.emit(_types.ProductsHooks.onProductsPriceApplied, processedProducts);
|
|
547
585
|
}
|
|
548
|
-
(0,
|
|
586
|
+
(0, _product.perfMark)('prepareProductsWithPrice', performance.now() - tTotal, {
|
|
549
587
|
productCount: products.length,
|
|
550
588
|
formatterCount: this.formatters.length,
|
|
551
|
-
mode: isIncremental ?
|
|
589
|
+
mode: isIncremental ? 'incremental' : 'full'
|
|
552
590
|
});
|
|
553
591
|
return processedProducts;
|
|
554
592
|
} catch (err) {
|
|
555
593
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
556
|
-
|
|
557
|
-
this.logError("prepareProductsWithPrice 处理失败", {
|
|
594
|
+
this.logError('prepareProductsWithPrice 处理失败', {
|
|
558
595
|
schedule_date,
|
|
559
596
|
error: errorMessage
|
|
560
597
|
});
|
|
@@ -562,31 +599,28 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
562
599
|
return [];
|
|
563
600
|
}
|
|
564
601
|
getProductsPriceCacheKey(schedule_date, extraContext, productIds) {
|
|
565
|
-
const datetime =
|
|
566
|
-
const productScope = productIds ? `:products:${productIds.join(
|
|
567
|
-
const strategyScope = this.stringifyStable(
|
|
568
|
-
return `${datetime}:customer:${
|
|
602
|
+
const datetime = extraContext?.schedule_datetime || schedule_date;
|
|
603
|
+
const productScope = productIds ? `:products:${productIds.join(',')}` : '';
|
|
604
|
+
const strategyScope = this.stringifyStable(extraContext?.strategy_context || {});
|
|
605
|
+
return `${datetime}:customer:${extraContext?.customer_id ?? ''}:strategy:${strategyScope}${productScope}`;
|
|
569
606
|
}
|
|
607
|
+
|
|
570
608
|
/**
|
|
571
609
|
* 稳定序列化策略上下文,避免对象 key 顺序不同导致缓存 key 抖动。
|
|
572
610
|
*/
|
|
573
611
|
stringifyStable(value) {
|
|
574
|
-
if (value == null)
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
return `
|
|
578
|
-
if (typeof value === "object") {
|
|
579
|
-
return `{${Object.keys(value).sort().map((key) => `${key}:${this.stringifyStable(value[key])}`).join(",")}}`;
|
|
612
|
+
if (value == null) return '';
|
|
613
|
+
if (Array.isArray(value)) return `[${value.map(item => this.stringifyStable(item)).join(',')}]`;
|
|
614
|
+
if (typeof value === 'object') {
|
|
615
|
+
return `{${Object.keys(value).sort().map(key => `${key}:${this.stringifyStable(value[key])}`).join(',')}}`;
|
|
580
616
|
}
|
|
581
617
|
return String(value);
|
|
582
618
|
}
|
|
583
619
|
normalizeProductIds(productIds) {
|
|
584
|
-
if (!Array.isArray(productIds))
|
|
585
|
-
|
|
586
|
-
return Array.from(new Set(
|
|
587
|
-
productIds.map((id) => Number(id)).filter((id) => Number.isFinite(id))
|
|
588
|
-
)).sort((a, b) => a - b);
|
|
620
|
+
if (!Array.isArray(productIds)) return undefined;
|
|
621
|
+
return Array.from(new Set(productIds.map(id => Number(id)).filter(id => Number.isFinite(id)))).sort((a, b) => a - b);
|
|
589
622
|
}
|
|
623
|
+
|
|
590
624
|
/**
|
|
591
625
|
* 按轻量范围返回商品 ID,不做 structuredClone。
|
|
592
626
|
*/
|
|
@@ -594,29 +628,24 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
594
628
|
const productIds = this.normalizeProductIds(scope.productIds) || [];
|
|
595
629
|
const collectionIds = this.normalizeProductIds(scope.collectionIds) || [];
|
|
596
630
|
if (scope.isAllProducts) {
|
|
597
|
-
return this.getProductsRef().map(
|
|
631
|
+
return this.getProductsRef().map(product => product.id);
|
|
598
632
|
}
|
|
599
|
-
if (productIds.length === 0 && collectionIds.length === 0)
|
|
600
|
-
|
|
601
|
-
const result = /* @__PURE__ */ new Set();
|
|
633
|
+
if (productIds.length === 0 && collectionIds.length === 0) return [];
|
|
634
|
+
const result = new Set();
|
|
602
635
|
for (const id of productIds) {
|
|
603
|
-
if (this.store.map.has(id))
|
|
604
|
-
result.add(id);
|
|
636
|
+
if (this.store.map.has(id)) result.add(id);
|
|
605
637
|
}
|
|
606
|
-
if (collectionIds.length === 0)
|
|
607
|
-
return Array.from(result);
|
|
638
|
+
if (collectionIds.length === 0) return Array.from(result);
|
|
608
639
|
const collectionIdSet = new Set(collectionIds);
|
|
609
640
|
for (const product of this.getProductsRef()) {
|
|
610
|
-
if (result.has(product.id))
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const isInCollection = product.collection.some((collection) => collectionIdSet.has(collection.id));
|
|
615
|
-
if (isInCollection)
|
|
616
|
-
result.add(product.id);
|
|
641
|
+
if (result.has(product.id)) continue;
|
|
642
|
+
if (!Array.isArray(product.collection)) continue;
|
|
643
|
+
const isInCollection = product.collection.some(collection => collectionIdSet.has(collection.id));
|
|
644
|
+
if (isInCollection) result.add(product.id);
|
|
617
645
|
}
|
|
618
646
|
return Array.from(result);
|
|
619
647
|
}
|
|
648
|
+
|
|
620
649
|
/**
|
|
621
650
|
* 应用所有已注册的格式化器
|
|
622
651
|
* @param products 商品列表
|
|
@@ -627,11 +656,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
627
656
|
async applyFormatters(products, context) {
|
|
628
657
|
let result = products;
|
|
629
658
|
if (this.formatters.length === 0) {
|
|
630
|
-
console.warn(
|
|
631
|
-
this.logWarning(
|
|
659
|
+
console.warn('[ProductsModule] ⚠️ 没有注册任何格式化器');
|
|
660
|
+
this.logWarning('没有注册任何格式化器', {
|
|
661
|
+
productCount: products.length
|
|
662
|
+
});
|
|
632
663
|
return result;
|
|
633
664
|
}
|
|
634
|
-
this.logInfo(
|
|
665
|
+
this.logInfo('开始应用格式化器', {
|
|
635
666
|
productCount: products.length,
|
|
636
667
|
formatterCount: this.formatters.length
|
|
637
668
|
});
|
|
@@ -640,7 +671,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
640
671
|
try {
|
|
641
672
|
const tF = performance.now();
|
|
642
673
|
result = await formatter(result, context);
|
|
643
|
-
(0,
|
|
674
|
+
(0, _product.perfMark)(`applyFormatters[${i}]`, performance.now() - tF, {
|
|
644
675
|
index: i,
|
|
645
676
|
total: this.formatters.length,
|
|
646
677
|
productCount: result.length
|
|
@@ -655,12 +686,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
655
686
|
});
|
|
656
687
|
}
|
|
657
688
|
}
|
|
658
|
-
this.logInfo(
|
|
689
|
+
this.logInfo('所有格式化器已应用', {
|
|
659
690
|
formatterCount: this.formatters.length,
|
|
660
691
|
resultProductCount: result.length
|
|
661
692
|
});
|
|
662
693
|
return result;
|
|
663
694
|
}
|
|
695
|
+
|
|
664
696
|
/**
|
|
665
697
|
* 注册内置商品格式化器。
|
|
666
698
|
*
|
|
@@ -669,16 +701,15 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
669
701
|
*/
|
|
670
702
|
registerBuiltinProductFormatters() {
|
|
671
703
|
if (this.isBuiltinFormatterRegistered) {
|
|
672
|
-
console.warn(
|
|
704
|
+
console.warn('[ProductsModule] 内置商品格式化器已注册,跳过');
|
|
673
705
|
return;
|
|
674
706
|
}
|
|
675
707
|
this.isLegacyPriceFormatterEnabled = this.shouldEnableLegacyPriceFormatter();
|
|
676
708
|
const dataVariantFormatter = (products, context) => {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (!evaluator || typeof evaluator.resolveProducts !== "function") {
|
|
709
|
+
const evaluator = context.dataVariantEvaluator || this.core?.context?.dataVariantEvaluator;
|
|
710
|
+
if (!evaluator || typeof evaluator.resolveProducts !== 'function') {
|
|
680
711
|
this.lastScheduleTimePoints = [];
|
|
681
|
-
this.logWarning(
|
|
712
|
+
this.logWarning('智能定价评估器未注入,跳过 DataVariant 格式化', {
|
|
682
713
|
productCount: products.length
|
|
683
714
|
});
|
|
684
715
|
return products;
|
|
@@ -697,22 +728,22 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
697
728
|
custom: strategyContext
|
|
698
729
|
};
|
|
699
730
|
const result = evaluator.resolveProducts(businessData);
|
|
700
|
-
this.lastScheduleTimePoints = Array.isArray(result
|
|
701
|
-
return Array.isArray(result
|
|
731
|
+
this.lastScheduleTimePoints = Array.isArray(result?.scheduleTimePoints) ? [...result.scheduleTimePoints] : [];
|
|
732
|
+
return Array.isArray(result?.products) ? result.products : products;
|
|
702
733
|
};
|
|
734
|
+
|
|
735
|
+
// 创建 legacy 报价单格式化器
|
|
703
736
|
const legacyPriceFormatter = (products, context) => {
|
|
704
737
|
if (!context.priceData || context.priceData.length === 0) {
|
|
705
|
-
console.log("[ProductsModule] 💰 没有价格数据,跳过 legacy 报价单价格应用");
|
|
706
738
|
return products;
|
|
707
739
|
}
|
|
708
|
-
|
|
709
|
-
return (0, import_product.applyPriceDataToProducts)(products, context.priceData);
|
|
740
|
+
return (0, _product.applyPriceDataToProducts)(products, context.priceData);
|
|
710
741
|
};
|
|
711
742
|
const i18nFormatter = (products, context) => {
|
|
712
|
-
return (0,
|
|
743
|
+
return (0, _product.applyI18nToProducts)(products, context.locale);
|
|
713
744
|
};
|
|
714
745
|
const detailValueFormatter = (products, context) => {
|
|
715
|
-
return (0,
|
|
746
|
+
return (0, _product.applyDetailValueToProducts)(products, context);
|
|
716
747
|
};
|
|
717
748
|
if (this.isLegacyPriceFormatterEnabled) {
|
|
718
749
|
this.formatters.push(legacyPriceFormatter);
|
|
@@ -721,17 +752,17 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
721
752
|
this.formatters.push(i18nFormatter);
|
|
722
753
|
this.formatters.push(detailValueFormatter);
|
|
723
754
|
this.isBuiltinFormatterRegistered = true;
|
|
724
|
-
console.log("[ProductsModule] ✅ 内置商品格式化器已注册(智能定价优先)");
|
|
725
755
|
}
|
|
756
|
+
|
|
726
757
|
/**
|
|
727
758
|
* 是否启用旧报价单价格 formatter。
|
|
728
759
|
*
|
|
729
760
|
* 默认关闭,避免报价单逻辑覆盖智能定价结果。
|
|
730
761
|
*/
|
|
731
762
|
shouldEnableLegacyPriceFormatter() {
|
|
732
|
-
|
|
733
|
-
return ((_a = this.otherParams) == null ? void 0 : _a.enableLegacyPriceFormatter) === true || ((_c = (_b = this.core) == null ? void 0 : _b.context) == null ? void 0 : _c.enableLegacyPriceFormatter) === true;
|
|
763
|
+
return this.otherParams?.enableLegacyPriceFormatter === true || this.core?.context?.enableLegacyPriceFormatter === true;
|
|
734
764
|
}
|
|
765
|
+
|
|
735
766
|
/**
|
|
736
767
|
* 返回最近一次 prepareProductsWithPrice / DataVariant 评估提取的变价时间点(HH:mm)。
|
|
737
768
|
* 供 OS Server 商品 query 订阅定时刷新使用。
|
|
@@ -739,6 +770,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
739
770
|
getLastScheduleTimePoints() {
|
|
740
771
|
return [...this.lastScheduleTimePoints];
|
|
741
772
|
}
|
|
773
|
+
|
|
742
774
|
/**
|
|
743
775
|
* 注册商品格式化器
|
|
744
776
|
* 格式化器会按注册顺序依次执行;legacy 启用时先兜底报价,再由智能定价覆盖。
|
|
@@ -756,29 +788,30 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
756
788
|
*/
|
|
757
789
|
registerFormatter(formatter, prepend = false) {
|
|
758
790
|
if (prepend) {
|
|
791
|
+
// 插入到内置智能定价格式化器之后;legacy 报价单启用时需跳过它。
|
|
759
792
|
const insertIndex = this.isBuiltinFormatterRegistered ? this.isLegacyPriceFormatterEnabled ? 2 : 1 : 0;
|
|
760
793
|
this.formatters.splice(insertIndex, 0, formatter);
|
|
761
|
-
console.log(`[ProductsModule] 📌 已在位置 ${insertIndex + 1} 插入格式化器,当前共 ${this.formatters.length} 个`);
|
|
762
794
|
} else {
|
|
795
|
+
// 追加到末尾
|
|
763
796
|
this.formatters.push(formatter);
|
|
764
|
-
console.log(`[ProductsModule] 📌 已注册格式化器,当前共 ${this.formatters.length} 个`);
|
|
765
797
|
}
|
|
766
798
|
}
|
|
799
|
+
|
|
767
800
|
/**
|
|
768
801
|
* 清空所有格式化器(包括内置智能定价格式化器)
|
|
769
802
|
* @param keepBuiltin 是否保留内置智能定价格式化器(默认 true)
|
|
770
803
|
*/
|
|
771
804
|
clearFormatters(keepBuiltin = true) {
|
|
772
805
|
if (keepBuiltin && this.isBuiltinFormatterRegistered) {
|
|
806
|
+
// 保留 legacy 兜底(如果启用)和智能定价格式化器。
|
|
773
807
|
const builtinCount = this.isLegacyPriceFormatterEnabled ? 2 : 1;
|
|
774
808
|
this.formatters = this.formatters.slice(0, builtinCount);
|
|
775
|
-
console.log("[ProductsModule] 🧹 已清空自定义格式化器,保留内置智能定价格式化器");
|
|
776
809
|
} else {
|
|
777
810
|
this.formatters = [];
|
|
778
811
|
this.isBuiltinFormatterRegistered = false;
|
|
779
|
-
console.log("[ProductsModule] 🧹 已清空所有格式化器");
|
|
780
812
|
}
|
|
781
813
|
}
|
|
814
|
+
|
|
782
815
|
/**
|
|
783
816
|
* 清理过期的价格缓存
|
|
784
817
|
* 当缓存数量超过限制时,删除最老的缓存
|
|
@@ -789,59 +822,72 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
789
822
|
const keys = Array.from(this.productsPriceCache.keys());
|
|
790
823
|
const oldestKey = keys[0];
|
|
791
824
|
this.productsPriceCache.delete(oldestKey);
|
|
792
|
-
console.log(`[ProductsModule] 🧹 清理过期商品价格缓存: ${oldestKey}`);
|
|
793
825
|
}
|
|
794
826
|
}
|
|
827
|
+
|
|
795
828
|
/**
|
|
796
829
|
* 清空商品价格缓存
|
|
797
830
|
* 可用于手动刷新价格数据
|
|
798
831
|
*/
|
|
799
832
|
clearPriceCache() {
|
|
800
833
|
this.productsPriceCache.clear();
|
|
801
|
-
|
|
834
|
+
this.quotationScheduleCache.clear();
|
|
835
|
+
this.quotationScheduleCacheLoaded = false;
|
|
802
836
|
}
|
|
837
|
+
|
|
803
838
|
/**
|
|
804
839
|
* 通过 ProductDataSource SSE 加载完整商品列表
|
|
805
840
|
*/
|
|
806
|
-
async loadProductsByServer() {
|
|
841
|
+
async loadProductsByServer(options = {}) {
|
|
807
842
|
if (!this.productDataSource) {
|
|
808
|
-
this.logWarning(
|
|
843
|
+
this.logWarning('loadProductsByServer: ProductDataSource 不可用');
|
|
809
844
|
return [];
|
|
810
845
|
}
|
|
811
|
-
|
|
846
|
+
const trigger = options.trigger || 'unknown';
|
|
847
|
+
this.logInfo('开始通过 DataSource SSE 加载商品列表', {
|
|
848
|
+
trigger
|
|
849
|
+
});
|
|
812
850
|
const t0 = performance.now();
|
|
813
851
|
try {
|
|
814
852
|
const tSSE = performance.now();
|
|
815
|
-
const productList = await this.productDataSource.run({
|
|
853
|
+
const productList = await this.productDataSource.run({
|
|
854
|
+
sse: {}
|
|
855
|
+
});
|
|
816
856
|
const list = productList || [];
|
|
817
|
-
(0,
|
|
818
|
-
|
|
857
|
+
(0, _product.perfMark)('loadProductsByServer.SSE', performance.now() - tSSE, {
|
|
858
|
+
count: list.length
|
|
859
|
+
});
|
|
860
|
+
this.logInfo('通过 DataSource SSE 加载商品列表成功', {
|
|
861
|
+
trigger,
|
|
819
862
|
productCount: list.length,
|
|
820
863
|
duration: `${Math.round(performance.now() - t0)}ms`
|
|
821
864
|
});
|
|
822
|
-
await this.replaceProductsSnapshotInIndexDB(list,
|
|
865
|
+
await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer');
|
|
823
866
|
this.lastServerFullFetchAtMs = Date.now();
|
|
824
|
-
await this.core.effects.emit(
|
|
825
|
-
(0,
|
|
867
|
+
await this.core.effects.emit(_types.ProductsHooks.onProductsLoaded, list);
|
|
868
|
+
(0, _product.perfMark)('loadProductsByServer', performance.now() - t0, {
|
|
869
|
+
count: list.length
|
|
870
|
+
});
|
|
826
871
|
return list;
|
|
827
872
|
} catch (error) {
|
|
828
873
|
const duration = Math.round(performance.now() - t0);
|
|
829
874
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
830
|
-
console.error(
|
|
831
|
-
this.logError(
|
|
875
|
+
console.error('[Products] 加载商品数据失败:', error);
|
|
876
|
+
this.logError('通过 DataSource SSE 加载商品列表失败', {
|
|
877
|
+
trigger,
|
|
832
878
|
duration: `${duration}ms`,
|
|
833
879
|
error: errorMessage
|
|
834
880
|
});
|
|
835
881
|
return [];
|
|
836
882
|
}
|
|
837
883
|
}
|
|
884
|
+
|
|
838
885
|
/**
|
|
839
886
|
* 纯请求方法:通过 HTTP 接口获取商品列表(无副作用,不触发事件、不写 IndexDB)
|
|
840
887
|
* @param params 查询参数
|
|
841
888
|
* @returns 商品列表
|
|
842
889
|
*/
|
|
843
890
|
async fetchProductsByHttp(params) {
|
|
844
|
-
var _a, _b;
|
|
845
891
|
const {
|
|
846
892
|
category_ids = [],
|
|
847
893
|
product_ids = [],
|
|
@@ -849,7 +895,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
849
895
|
customer_id,
|
|
850
896
|
cacheId
|
|
851
897
|
} = params || {};
|
|
852
|
-
this.logInfo(
|
|
898
|
+
this.logInfo('fetchProductsByHttp: 开始请求', {
|
|
853
899
|
categoryIdsCount: category_ids.length,
|
|
854
900
|
productIdsCount: product_ids.length,
|
|
855
901
|
collectionCount: Array.isArray(collection) ? collection.length : collection ? 1 : 0,
|
|
@@ -857,46 +903,29 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
857
903
|
});
|
|
858
904
|
const startTime = Date.now();
|
|
859
905
|
try {
|
|
860
|
-
const productsData = await this.request.post(
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
],
|
|
882
|
-
open_deposit: 1,
|
|
883
|
-
is_append_product_description: 1,
|
|
884
|
-
with_count: ["bundleGroup", "optionGroup"],
|
|
885
|
-
with_category_tree: 1,
|
|
886
|
-
status: "published",
|
|
887
|
-
num: 500,
|
|
888
|
-
skip: 1,
|
|
889
|
-
category_ids,
|
|
890
|
-
ids: product_ids,
|
|
891
|
-
collection,
|
|
892
|
-
front_end_cache_id: cacheId,
|
|
893
|
-
application_code: (_a = this.otherParams) == null ? void 0 : _a.channel
|
|
894
|
-
},
|
|
895
|
-
{ cache: void 0 }
|
|
896
|
-
);
|
|
897
|
-
const productList = ((_b = productsData == null ? void 0 : productsData.data) == null ? void 0 : _b.list) || [];
|
|
906
|
+
const productsData = await this.request.post(`/product/query`, {
|
|
907
|
+
open_bundle: 1,
|
|
908
|
+
exclude_extension_type: ['product_party', 'product_event', 'product_series_event', 'product_package_ticket', 'ticket', 'event_item'],
|
|
909
|
+
force_ignore_cache_flag: 1,
|
|
910
|
+
with: ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'],
|
|
911
|
+
open_deposit: 1,
|
|
912
|
+
is_append_product_description: 1,
|
|
913
|
+
with_count: ['bundleGroup', 'optionGroup'],
|
|
914
|
+
with_category_tree: 1,
|
|
915
|
+
status: 'published',
|
|
916
|
+
num: 500,
|
|
917
|
+
skip: 1,
|
|
918
|
+
category_ids,
|
|
919
|
+
ids: product_ids,
|
|
920
|
+
collection,
|
|
921
|
+
front_end_cache_id: cacheId,
|
|
922
|
+
application_code: this.otherParams?.channel
|
|
923
|
+
}, {
|
|
924
|
+
cache: undefined
|
|
925
|
+
});
|
|
926
|
+
const productList = productsData?.data?.list || [];
|
|
898
927
|
const duration = Date.now() - startTime;
|
|
899
|
-
this.logInfo(
|
|
928
|
+
this.logInfo('fetchProductsByHttp: 请求成功', {
|
|
900
929
|
productCount: productList.length,
|
|
901
930
|
duration: `${duration}ms`
|
|
902
931
|
});
|
|
@@ -904,14 +933,15 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
904
933
|
} catch (error) {
|
|
905
934
|
const duration = Date.now() - startTime;
|
|
906
935
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
907
|
-
console.error(
|
|
908
|
-
this.logError(
|
|
936
|
+
console.error('[Products] fetchProductsByHttp 失败:', error);
|
|
937
|
+
this.logError('fetchProductsByHttp: 请求失败', {
|
|
909
938
|
duration: `${duration}ms`,
|
|
910
939
|
error: errorMessage
|
|
911
940
|
});
|
|
912
941
|
return [];
|
|
913
942
|
}
|
|
914
943
|
}
|
|
944
|
+
|
|
915
945
|
/**
|
|
916
946
|
* 加载完整商品列表通过接口(包含所有详细数据)
|
|
917
947
|
* 包含副作用:保存到 IndexDB + 触发 onProductsLoaded 事件
|
|
@@ -920,34 +950,36 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
920
950
|
async loadProductsByServerHttp(params) {
|
|
921
951
|
const productList = await this.fetchProductsByHttp(params);
|
|
922
952
|
if (productList.length > 0) {
|
|
923
|
-
await this.replaceProductsSnapshotInIndexDB(productList,
|
|
953
|
+
await this.replaceProductsSnapshotInIndexDB(productList, 'loadProductsByServerHttp');
|
|
924
954
|
this.lastServerFullFetchAtMs = Date.now();
|
|
925
|
-
await this.core.effects.emit(
|
|
955
|
+
await this.core.effects.emit(_types.ProductsHooks.onProductsLoaded, productList);
|
|
926
956
|
}
|
|
927
957
|
return productList;
|
|
928
958
|
}
|
|
959
|
+
|
|
929
960
|
/**
|
|
930
961
|
* 获取商品列表(深拷贝,供外部安全使用)
|
|
931
962
|
*/
|
|
932
963
|
async getProducts() {
|
|
933
964
|
const t0 = performance.now();
|
|
934
965
|
const result = structuredClone(this.store.list);
|
|
935
|
-
(0,
|
|
966
|
+
(0, _product.perfMark)('ProductsModule.getProducts(structuredClone)', performance.now() - t0, {
|
|
967
|
+
count: result.length
|
|
968
|
+
});
|
|
936
969
|
return result;
|
|
937
970
|
}
|
|
971
|
+
|
|
938
972
|
/**
|
|
939
973
|
* 批量获取商品轻量快照,供订单详情等响应补齐展示协议字段。
|
|
940
974
|
*/
|
|
941
975
|
async getProductSnapshotsByIds(ids) {
|
|
942
976
|
const snapshots = {};
|
|
943
|
-
const seen =
|
|
977
|
+
const seen = new Set();
|
|
944
978
|
for (const id of ids) {
|
|
945
|
-
if (!Number.isFinite(id) || seen.has(id))
|
|
946
|
-
continue;
|
|
979
|
+
if (!Number.isFinite(id) || seen.has(id)) continue;
|
|
947
980
|
seen.add(id);
|
|
948
981
|
const product = this.store.map.get(id);
|
|
949
|
-
if (!product)
|
|
950
|
-
continue;
|
|
982
|
+
if (!product) continue;
|
|
951
983
|
snapshots[String(id)] = {
|
|
952
984
|
cover: product.cover,
|
|
953
985
|
holder_config: product.holder_config
|
|
@@ -955,6 +987,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
955
987
|
}
|
|
956
988
|
return snapshots;
|
|
957
989
|
}
|
|
990
|
+
|
|
958
991
|
/**
|
|
959
992
|
* 内部获取商品列表的直接引用(无拷贝)
|
|
960
993
|
* 仅供内部 formatter 流程使用,因为 formatter 会创建新对象
|
|
@@ -962,6 +995,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
962
995
|
getProductsRef() {
|
|
963
996
|
return this.store.list;
|
|
964
997
|
}
|
|
998
|
+
|
|
965
999
|
/**
|
|
966
1000
|
* 按 ID 从 Map 取商品引用,供内部 formatter 流程使用。
|
|
967
1001
|
*/
|
|
@@ -969,27 +1003,31 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
969
1003
|
const products = [];
|
|
970
1004
|
for (const id of ids) {
|
|
971
1005
|
const product = this.store.map.get(id);
|
|
972
|
-
if (product)
|
|
973
|
-
products.push(product);
|
|
1006
|
+
if (product) products.push(product);
|
|
974
1007
|
}
|
|
975
1008
|
return products;
|
|
976
1009
|
}
|
|
1010
|
+
|
|
977
1011
|
/**
|
|
978
1012
|
* 根据ID获取单个商品(从内存缓存)
|
|
979
1013
|
* 使用 Map 快速查询,时间复杂度 O(1)
|
|
980
1014
|
*/
|
|
981
1015
|
async getProductById(id) {
|
|
982
1016
|
const product = this.store.map.get(id);
|
|
983
|
-
return product ? structuredClone(product) :
|
|
1017
|
+
return product ? structuredClone(product) : undefined;
|
|
984
1018
|
}
|
|
1019
|
+
|
|
985
1020
|
/**
|
|
986
1021
|
* 根据 ID 列表删除商品(用于 pubsub 同步删除场景)
|
|
987
1022
|
* 同时更新 store.list、store.map、IndexDB 和价格缓存
|
|
988
1023
|
*/
|
|
989
1024
|
async removeProductsByIds(ids) {
|
|
990
1025
|
const idSet = new Set(ids);
|
|
991
|
-
this.logInfo(
|
|
992
|
-
|
|
1026
|
+
this.logInfo('removeProductsByIds', {
|
|
1027
|
+
ids,
|
|
1028
|
+
count: ids.length
|
|
1029
|
+
});
|
|
1030
|
+
this.store.list = this.store.list.filter(p => !idSet.has(p.id));
|
|
993
1031
|
for (const id of ids) {
|
|
994
1032
|
this.store.map.delete(id);
|
|
995
1033
|
}
|
|
@@ -1000,85 +1038,107 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1000
1038
|
}
|
|
1001
1039
|
} catch (error) {
|
|
1002
1040
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1003
|
-
this.logError(
|
|
1041
|
+
this.logError('removeProductsByIds: IndexDB 删除失败', {
|
|
1042
|
+
ids,
|
|
1043
|
+
error: errorMessage
|
|
1044
|
+
});
|
|
1004
1045
|
}
|
|
1005
1046
|
}
|
|
1006
1047
|
for (const [dateKey, cachedProducts] of this.productsPriceCache.entries()) {
|
|
1007
|
-
this.productsPriceCache.set(
|
|
1008
|
-
dateKey,
|
|
1009
|
-
cachedProducts.filter((p) => !idSet.has(p.id))
|
|
1010
|
-
);
|
|
1048
|
+
this.productsPriceCache.set(dateKey, cachedProducts.filter(p => !idSet.has(p.id)));
|
|
1011
1049
|
}
|
|
1012
|
-
this.core.effects.emit(
|
|
1013
|
-
this.logInfo(
|
|
1050
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1051
|
+
this.logInfo('removeProductsByIds 完成', {
|
|
1052
|
+
remaining: this.store.list.length
|
|
1053
|
+
});
|
|
1014
1054
|
}
|
|
1055
|
+
|
|
1015
1056
|
/**
|
|
1016
1057
|
* 重新从服务器加载全量商品列表并更新本地 store
|
|
1017
1058
|
* 用于 pubsub 同步 create / update / batch_update 场景
|
|
1018
1059
|
*/
|
|
1019
1060
|
async refreshProducts() {
|
|
1020
1061
|
const tTotal = performance.now();
|
|
1021
|
-
this.logInfo(
|
|
1022
|
-
const products = await this.loadProductsByServer(
|
|
1062
|
+
this.logInfo('refreshProducts 开始');
|
|
1063
|
+
const products = await this.loadProductsByServer({
|
|
1064
|
+
trigger: 'product_sync'
|
|
1065
|
+
});
|
|
1023
1066
|
if (products && products.length > 0) {
|
|
1067
|
+
// 服务器返回的数据已经是全新的,无需 cloneDeep
|
|
1024
1068
|
this.store.list = products;
|
|
1025
1069
|
this.syncProductsMap();
|
|
1026
|
-
this.core.effects.emit(
|
|
1027
|
-
this.logInfo(
|
|
1070
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1071
|
+
this.logInfo('refreshProducts 完成', {
|
|
1072
|
+
productCount: products.length
|
|
1073
|
+
});
|
|
1028
1074
|
} else {
|
|
1029
|
-
this.logWarning(
|
|
1075
|
+
this.logWarning('refreshProducts: 服务器未返回数据');
|
|
1030
1076
|
}
|
|
1031
|
-
(0,
|
|
1077
|
+
(0, _product.perfMark)('refreshProducts', performance.now() - tTotal, {
|
|
1078
|
+
count: this.store.list.length
|
|
1079
|
+
});
|
|
1032
1080
|
return this.store.list;
|
|
1033
1081
|
}
|
|
1082
|
+
|
|
1034
1083
|
/**
|
|
1035
1084
|
* 清空缓存
|
|
1036
1085
|
*/
|
|
1037
1086
|
async clear() {
|
|
1038
|
-
this.logInfo(
|
|
1087
|
+
this.logInfo('开始清空缓存', {
|
|
1039
1088
|
currentProductCount: this.store.list.length,
|
|
1040
1089
|
priceCacheCount: this.productsPriceCache.size
|
|
1041
1090
|
});
|
|
1042
1091
|
this.store.list = [];
|
|
1043
1092
|
this.store.map.clear();
|
|
1093
|
+
|
|
1094
|
+
// 同时清空 IndexDB 中的所有数据(包括商品和元数据)
|
|
1044
1095
|
if (this.dbManager) {
|
|
1045
1096
|
try {
|
|
1046
1097
|
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
1047
|
-
|
|
1048
|
-
this.logInfo("IndexDB 缓存已清空");
|
|
1098
|
+
this.logInfo('IndexDB 缓存已清空');
|
|
1049
1099
|
} catch (error) {
|
|
1050
1100
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1051
|
-
console.error(
|
|
1052
|
-
this.logError(
|
|
1101
|
+
console.error('[Products] 清空 IndexDB 缓存失败:', error);
|
|
1102
|
+
this.logError('清空 IndexDB 缓存失败', {
|
|
1103
|
+
error: errorMessage
|
|
1104
|
+
});
|
|
1053
1105
|
}
|
|
1054
1106
|
}
|
|
1055
|
-
|
|
1056
|
-
this.logInfo("缓存清空完成");
|
|
1107
|
+
this.logInfo('缓存清空完成');
|
|
1057
1108
|
}
|
|
1109
|
+
|
|
1058
1110
|
/**
|
|
1059
1111
|
* 从 IndexDB 加载商品数据
|
|
1060
1112
|
* @private
|
|
1061
1113
|
*/
|
|
1062
1114
|
async loadProductsFromIndexDB() {
|
|
1063
1115
|
if (!this.dbManager) {
|
|
1064
|
-
this.logWarning(
|
|
1116
|
+
this.logWarning('loadProductsFromIndexDB: dbManager 不可用');
|
|
1065
1117
|
return [];
|
|
1066
1118
|
}
|
|
1119
|
+
const startedAt = performance.now();
|
|
1067
1120
|
try {
|
|
1068
|
-
const t0 = performance.now();
|
|
1069
1121
|
const products = await this.dbManager.getAll(INDEXDB_STORE_NAME);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1122
|
+
const durationMs = +(performance.now() - startedAt).toFixed(2);
|
|
1123
|
+
(0, _product.perfMark)('loadProductsFromIndexDB', durationMs, {
|
|
1124
|
+
count: products?.length ?? 0
|
|
1125
|
+
});
|
|
1126
|
+
this.logInfo('从 IndexDB 加载商品数据', {
|
|
1127
|
+
productCount: products?.length ?? 0,
|
|
1128
|
+
durationMs
|
|
1073
1129
|
});
|
|
1074
1130
|
return products || [];
|
|
1075
1131
|
} catch (error) {
|
|
1076
1132
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1077
|
-
console.error(
|
|
1078
|
-
this.logError(
|
|
1133
|
+
console.error('[Products] 从 IndexDB 读取数据失败:', error);
|
|
1134
|
+
this.logError('从 IndexDB 读取数据失败', {
|
|
1135
|
+
durationMs: +(performance.now() - startedAt).toFixed(2),
|
|
1136
|
+
error: errorMessage
|
|
1137
|
+
});
|
|
1079
1138
|
return [];
|
|
1080
1139
|
}
|
|
1081
1140
|
}
|
|
1141
|
+
|
|
1082
1142
|
/**
|
|
1083
1143
|
* 串行执行商品 IndexDB 写入,避免快照替换与增量 patch 并发交错。
|
|
1084
1144
|
*
|
|
@@ -1089,9 +1149,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1089
1149
|
*/
|
|
1090
1150
|
async runInProductIndexDBSaveQueue(task) {
|
|
1091
1151
|
const queuedTask = this.productIndexDBSaveQueue.then(task, task);
|
|
1092
|
-
this.productIndexDBSaveQueue = queuedTask.then(() =>
|
|
1152
|
+
this.productIndexDBSaveQueue = queuedTask.then(() => undefined, () => undefined);
|
|
1093
1153
|
return queuedTask;
|
|
1094
1154
|
}
|
|
1155
|
+
|
|
1095
1156
|
/**
|
|
1096
1157
|
* 全量快照写入 IndexDB:clear + bulkUpdate,仅用于 SSE/HTTP 全量拉取。
|
|
1097
1158
|
*
|
|
@@ -1100,29 +1161,64 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1100
1161
|
*/
|
|
1101
1162
|
async replaceProductsSnapshotInIndexDB(products, source) {
|
|
1102
1163
|
if (!this.dbManager) {
|
|
1103
|
-
this.logWarning(
|
|
1164
|
+
this.logWarning('replaceProductsSnapshotInIndexDB: dbManager 不可用');
|
|
1104
1165
|
return;
|
|
1105
1166
|
}
|
|
1167
|
+
const enqueuedAt = performance.now();
|
|
1168
|
+
let queueWaitMs = 0;
|
|
1169
|
+
let clearDurationMs = 0;
|
|
1170
|
+
let writeDurationMs = 0;
|
|
1106
1171
|
try {
|
|
1107
1172
|
await this.runInProductIndexDBSaveQueue(async () => {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1173
|
+
const operationStartedAt = performance.now();
|
|
1174
|
+
queueWaitMs = +(operationStartedAt - enqueuedAt).toFixed(2);
|
|
1175
|
+
this.logInfo('replaceProductsSnapshotInIndexDB 开始', {
|
|
1176
|
+
source,
|
|
1177
|
+
productCount: products.length,
|
|
1178
|
+
queueWaitMs
|
|
1179
|
+
});
|
|
1180
|
+
const clearStartedAt = performance.now();
|
|
1181
|
+
try {
|
|
1182
|
+
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
1183
|
+
} finally {
|
|
1184
|
+
clearDurationMs = +(performance.now() - clearStartedAt).toFixed(2);
|
|
1185
|
+
}
|
|
1111
1186
|
if (products.length > 0) {
|
|
1112
|
-
|
|
1187
|
+
const writeStartedAt = performance.now();
|
|
1188
|
+
try {
|
|
1189
|
+
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
|
|
1190
|
+
} finally {
|
|
1191
|
+
writeDurationMs = +(performance.now() - writeStartedAt).toFixed(2);
|
|
1192
|
+
}
|
|
1113
1193
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1194
|
+
const executeDurationMs = +(performance.now() - operationStartedAt).toFixed(2);
|
|
1195
|
+
(0, _product.perfMark)('replaceProductsSnapshotInIndexDB', executeDurationMs, {
|
|
1196
|
+
count: products.length
|
|
1197
|
+
});
|
|
1198
|
+
this.logInfo('replaceProductsSnapshotInIndexDB 完成', {
|
|
1199
|
+
source,
|
|
1200
|
+
productCount: products.length,
|
|
1201
|
+
queueWaitMs,
|
|
1202
|
+
clearDurationMs,
|
|
1203
|
+
writeDurationMs,
|
|
1204
|
+
executeDurationMs,
|
|
1205
|
+
totalDurationMs: +(performance.now() - enqueuedAt).toFixed(2)
|
|
1206
|
+
});
|
|
1116
1207
|
});
|
|
1117
1208
|
} catch (error) {
|
|
1118
1209
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1119
|
-
this.logError(
|
|
1210
|
+
this.logError('全量保存商品到 IndexDB 失败', {
|
|
1120
1211
|
source,
|
|
1121
1212
|
productCount: products.length,
|
|
1213
|
+
queueWaitMs,
|
|
1214
|
+
clearDurationMs,
|
|
1215
|
+
writeDurationMs,
|
|
1216
|
+
totalDurationMs: +(performance.now() - enqueuedAt).toFixed(2),
|
|
1122
1217
|
error: errorMessage
|
|
1123
1218
|
});
|
|
1124
1219
|
}
|
|
1125
1220
|
}
|
|
1221
|
+
|
|
1126
1222
|
/**
|
|
1127
1223
|
* 增量 upsert 商品到 IndexDB,不清空表;用于 pubsub 批次合并后的变更商品。
|
|
1128
1224
|
*
|
|
@@ -1133,33 +1229,42 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1133
1229
|
if (!this.dbManager || products.length === 0) {
|
|
1134
1230
|
return;
|
|
1135
1231
|
}
|
|
1136
|
-
const validProducts = products.filter(
|
|
1232
|
+
const validProducts = products.filter(p => p?.id !== undefined && p?.id !== null);
|
|
1137
1233
|
if (validProducts.length === 0) {
|
|
1138
1234
|
return;
|
|
1139
1235
|
}
|
|
1140
1236
|
try {
|
|
1141
1237
|
await this.runInProductIndexDBSaveQueue(async () => {
|
|
1142
|
-
this.logInfo(
|
|
1238
|
+
this.logInfo('patchProductsInIndexDB 开始', {
|
|
1239
|
+
source,
|
|
1240
|
+
count: validProducts.length
|
|
1241
|
+
});
|
|
1143
1242
|
const t0 = performance.now();
|
|
1144
|
-
if (typeof this.dbManager.bulkUpdate ===
|
|
1243
|
+
if (typeof this.dbManager.bulkUpdate === 'function') {
|
|
1145
1244
|
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, validProducts);
|
|
1146
|
-
} else if (typeof this.dbManager.update ===
|
|
1245
|
+
} else if (typeof this.dbManager.update === 'function') {
|
|
1147
1246
|
for (const product of validProducts) {
|
|
1148
1247
|
await this.dbManager.update(INDEXDB_STORE_NAME, product);
|
|
1149
1248
|
}
|
|
1150
1249
|
}
|
|
1151
|
-
(0,
|
|
1152
|
-
|
|
1250
|
+
(0, _product.perfMark)('patchProductsInIndexDB', performance.now() - t0, {
|
|
1251
|
+
count: validProducts.length
|
|
1252
|
+
});
|
|
1253
|
+
this.logInfo('patchProductsInIndexDB 完成', {
|
|
1254
|
+
source,
|
|
1255
|
+
count: validProducts.length
|
|
1256
|
+
});
|
|
1153
1257
|
});
|
|
1154
1258
|
} catch (error) {
|
|
1155
1259
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1156
|
-
this.logError(
|
|
1260
|
+
this.logError('增量保存商品到 IndexDB 失败', {
|
|
1157
1261
|
source,
|
|
1158
1262
|
error: errorMessage,
|
|
1159
1263
|
productCount: validProducts.length
|
|
1160
1264
|
});
|
|
1161
1265
|
}
|
|
1162
1266
|
}
|
|
1267
|
+
|
|
1163
1268
|
/**
|
|
1164
1269
|
* 同步更新商品 Map 缓存
|
|
1165
1270
|
* 将 list 中的商品同步到 map,以 id 为 key
|
|
@@ -1171,92 +1276,109 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1171
1276
|
for (const product of this.store.list) {
|
|
1172
1277
|
this.store.map.set(product.id, product);
|
|
1173
1278
|
}
|
|
1174
|
-
(0,
|
|
1279
|
+
(0, _product.perfMark)('syncProductsMap', performance.now() - t0, {
|
|
1280
|
+
count: this.store.map.size
|
|
1281
|
+
});
|
|
1175
1282
|
}
|
|
1283
|
+
|
|
1176
1284
|
/**
|
|
1177
1285
|
* 预加载模块数据(统一接口)
|
|
1178
1286
|
* 在模块注册后自动调用
|
|
1179
1287
|
*/
|
|
1180
1288
|
async preload() {
|
|
1181
|
-
console.log("[Products] 开始预加载数据...");
|
|
1182
1289
|
const tTotal = performance.now();
|
|
1183
|
-
this.logInfo(
|
|
1290
|
+
this.logInfo('开始预加载数据');
|
|
1184
1291
|
try {
|
|
1185
1292
|
const tIndexDB = performance.now();
|
|
1186
1293
|
const cachedData = await this.loadProductsFromIndexDB();
|
|
1187
|
-
(0,
|
|
1294
|
+
(0, _product.perfMark)('preload.loadFromIndexDB', performance.now() - tIndexDB, {
|
|
1295
|
+
count: cachedData?.length ?? 0
|
|
1296
|
+
});
|
|
1188
1297
|
if (cachedData && cachedData.length > 0) {
|
|
1189
|
-
|
|
1298
|
+
// IndexDB 反序列化的数据已经是全新的,无需 cloneDeep
|
|
1190
1299
|
this.store.list = cachedData;
|
|
1191
1300
|
const tSync = performance.now();
|
|
1192
1301
|
this.syncProductsMap();
|
|
1193
|
-
(0,
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
)
|
|
1198
|
-
(0, import_product.perfMark)("preload(IndexDB)", performance.now() - tTotal, {
|
|
1302
|
+
(0, _product.perfMark)('preload.syncProductsMap', performance.now() - tSync, {
|
|
1303
|
+
count: cachedData.length
|
|
1304
|
+
});
|
|
1305
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1306
|
+
(0, _product.perfMark)('preload(IndexDB)', performance.now() - tTotal, {
|
|
1199
1307
|
count: cachedData.length,
|
|
1200
|
-
source:
|
|
1308
|
+
source: 'IndexDB'
|
|
1201
1309
|
});
|
|
1202
|
-
this.logInfo(
|
|
1310
|
+
this.logInfo('预加载完成(从 IndexDB)', {
|
|
1311
|
+
trigger: 'preload',
|
|
1203
1312
|
productCount: cachedData.length,
|
|
1204
1313
|
duration: `${Math.round(performance.now() - tTotal)}ms`,
|
|
1205
|
-
source:
|
|
1314
|
+
source: 'IndexDB'
|
|
1206
1315
|
});
|
|
1207
1316
|
this.lastPreloadCompletedAtMs = Date.now();
|
|
1208
1317
|
return;
|
|
1209
1318
|
}
|
|
1210
|
-
|
|
1211
|
-
this.logInfo("IndexDB 中没有缓存数据,准备从服务器加载");
|
|
1319
|
+
this.logInfo('IndexDB 中没有缓存数据,准备从服务器加载');
|
|
1212
1320
|
} catch (error) {
|
|
1213
1321
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1214
|
-
console.warn(
|
|
1215
|
-
this.logWarning(
|
|
1322
|
+
console.warn('[Products] 从 IndexDB 加载数据失败:', error);
|
|
1323
|
+
this.logWarning('从 IndexDB 加载数据失败,准备从服务器加载', {
|
|
1324
|
+
error: errorMessage
|
|
1325
|
+
});
|
|
1216
1326
|
}
|
|
1217
1327
|
const tServer = performance.now();
|
|
1218
|
-
const products = await this.loadProductsByServer(
|
|
1219
|
-
|
|
1328
|
+
const products = await this.loadProductsByServer({
|
|
1329
|
+
trigger: 'preload'
|
|
1330
|
+
});
|
|
1331
|
+
(0, _product.perfMark)('preload.loadFromServer', performance.now() - tServer, {
|
|
1332
|
+
count: products?.length ?? 0
|
|
1333
|
+
});
|
|
1220
1334
|
if (products && products.length > 0) {
|
|
1335
|
+
// 服务器返回的数据已经是全新的,无需 cloneDeep
|
|
1221
1336
|
this.store.list = products;
|
|
1222
1337
|
const tSync = performance.now();
|
|
1223
1338
|
this.syncProductsMap();
|
|
1224
|
-
(0,
|
|
1225
|
-
|
|
1226
|
-
|
|
1339
|
+
(0, _product.perfMark)('preload.syncProductsMap', performance.now() - tSync, {
|
|
1340
|
+
count: products.length
|
|
1341
|
+
});
|
|
1342
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1343
|
+
(0, _product.perfMark)('preload(Server)', performance.now() - tTotal, {
|
|
1227
1344
|
count: products.length,
|
|
1228
|
-
source:
|
|
1345
|
+
source: 'Server'
|
|
1229
1346
|
});
|
|
1230
|
-
this.logInfo(
|
|
1347
|
+
this.logInfo('预加载完成(从服务器)', {
|
|
1348
|
+
trigger: 'preload',
|
|
1231
1349
|
productCount: products.length,
|
|
1232
1350
|
duration: `${Math.round(performance.now() - tTotal)}ms`,
|
|
1233
|
-
source:
|
|
1351
|
+
source: 'Server'
|
|
1234
1352
|
});
|
|
1235
1353
|
this.lastPreloadCompletedAtMs = Date.now();
|
|
1236
1354
|
} else {
|
|
1237
|
-
(0,
|
|
1238
|
-
|
|
1355
|
+
(0, _product.perfMark)('preload(empty)', performance.now() - tTotal, {
|
|
1356
|
+
source: 'empty'
|
|
1357
|
+
});
|
|
1358
|
+
this.logWarning('预加载完成但未获取到数据', {
|
|
1239
1359
|
duration: `${Math.round(performance.now() - tTotal)}ms`
|
|
1240
1360
|
});
|
|
1241
1361
|
}
|
|
1242
1362
|
}
|
|
1363
|
+
|
|
1243
1364
|
// =============================================
|
|
1244
1365
|
// 商品数据同步(pubsub)
|
|
1245
1366
|
// =============================================
|
|
1367
|
+
|
|
1246
1368
|
/**
|
|
1247
1369
|
* 初始化 ProductDataSource 实例
|
|
1248
1370
|
* 与 pubsub 订阅和数据获取分开,仅负责创建实例
|
|
1249
1371
|
*/
|
|
1250
1372
|
initProductDataSource() {
|
|
1251
|
-
|
|
1252
|
-
const ProductDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.ProductDataSource;
|
|
1373
|
+
const ProductDataSourceClass = this.core.serverOptions?.All_DATA_SOURCES?.ProductDataSource;
|
|
1253
1374
|
if (!ProductDataSourceClass) {
|
|
1254
|
-
this.logWarning(
|
|
1375
|
+
this.logWarning('initProductDataSource: ProductDataSource 不可用');
|
|
1255
1376
|
return;
|
|
1256
1377
|
}
|
|
1257
1378
|
this.productDataSource = new ProductDataSourceClass();
|
|
1258
|
-
this.logInfo(
|
|
1379
|
+
this.logInfo('ProductDataSource 实例已创建');
|
|
1259
1380
|
}
|
|
1381
|
+
|
|
1260
1382
|
/**
|
|
1261
1383
|
* 初始化 pubsub 订阅,监听管理后台商品变更
|
|
1262
1384
|
* 仅负责订阅 product / product_quotation 频道,消息通过防抖合并后批量处理
|
|
@@ -1264,21 +1386,22 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1264
1386
|
*/
|
|
1265
1387
|
setupProductSync() {
|
|
1266
1388
|
if (!this.productDataSource) {
|
|
1267
|
-
this.logWarning(
|
|
1389
|
+
this.logWarning('setupProductSync: ProductDataSource 不可用,跳过同步初始化');
|
|
1268
1390
|
return;
|
|
1269
1391
|
}
|
|
1270
|
-
const createHandler =
|
|
1271
|
-
const data =
|
|
1272
|
-
if (!data)
|
|
1273
|
-
|
|
1274
|
-
console.log(`[ProductsModule] 收到同步消息 [${channelKey}]:`, data);
|
|
1275
|
-
this.logInfo("收到同步消息", {
|
|
1392
|
+
const createHandler = channelKey => message => {
|
|
1393
|
+
const data = message?.data || message;
|
|
1394
|
+
if (!data) return;
|
|
1395
|
+
this.logInfo('收到同步消息', {
|
|
1276
1396
|
channelKey,
|
|
1277
1397
|
action: data.action,
|
|
1278
1398
|
id: data.id,
|
|
1279
1399
|
action_filed: data.action_filed
|
|
1280
1400
|
});
|
|
1281
|
-
this.pendingSyncMessages.push({
|
|
1401
|
+
this.pendingSyncMessages.push({
|
|
1402
|
+
...data,
|
|
1403
|
+
_channelKey: channelKey
|
|
1404
|
+
});
|
|
1282
1405
|
if (this.syncTimer) {
|
|
1283
1406
|
clearTimeout(this.syncTimer);
|
|
1284
1407
|
}
|
|
@@ -1288,21 +1411,25 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1288
1411
|
};
|
|
1289
1412
|
this.productDataSource.run({
|
|
1290
1413
|
pubsub: {
|
|
1291
|
-
callback:
|
|
1292
|
-
|
|
1293
|
-
|
|
1414
|
+
callback: res => {
|
|
1415
|
+
// console.log('sse_products_callback', res)
|
|
1416
|
+
// this.logInfo('sse_products_callback: 收到同步消息', {
|
|
1417
|
+
// data: res?.data,
|
|
1418
|
+
// });
|
|
1419
|
+
if (!res?.data) return;
|
|
1294
1420
|
const data = res.data;
|
|
1295
|
-
const channelKey = data.module ||
|
|
1421
|
+
const channelKey = data.module || 'product';
|
|
1296
1422
|
createHandler(channelKey)(data);
|
|
1297
1423
|
}
|
|
1298
1424
|
}
|
|
1299
|
-
}).catch(
|
|
1300
|
-
this.logError(
|
|
1425
|
+
}).catch(err => {
|
|
1426
|
+
this.logError('setupProductSync: DataSource run 出错', {
|
|
1301
1427
|
error: err instanceof Error ? err.message : String(err)
|
|
1302
1428
|
});
|
|
1303
1429
|
});
|
|
1304
|
-
this.logInfo(
|
|
1430
|
+
this.logInfo('setupProductSync: pubsub 订阅已建立');
|
|
1305
1431
|
}
|
|
1432
|
+
|
|
1306
1433
|
/**
|
|
1307
1434
|
* 处理防抖后的同步消息批次
|
|
1308
1435
|
*
|
|
@@ -1322,62 +1449,65 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1322
1449
|
* Server 层监听该事件后对变更商品增量执行 prepareProductsWithPrice 并更新缓存
|
|
1323
1450
|
*/
|
|
1324
1451
|
async processProductSyncMessages() {
|
|
1325
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1326
1452
|
const messages = [...this.pendingSyncMessages];
|
|
1327
1453
|
this.pendingSyncMessages = [];
|
|
1328
|
-
if (messages.length === 0)
|
|
1329
|
-
|
|
1330
|
-
|
|
1454
|
+
if (messages.length === 0) return;
|
|
1455
|
+
this.logInfo('processProductSyncMessages: 开始处理', {
|
|
1456
|
+
count: messages.length
|
|
1457
|
+
});
|
|
1331
1458
|
const deleteIds = [];
|
|
1332
|
-
const bodyUpdates =
|
|
1459
|
+
const bodyUpdates = new Map();
|
|
1333
1460
|
const sseRefreshIds = [];
|
|
1334
1461
|
const priceRefreshIds = [];
|
|
1335
1462
|
let shouldClearPriceCache = false;
|
|
1336
1463
|
for (const msg of messages) {
|
|
1337
|
-
const channelKey = msg._channelKey || msg.module ||
|
|
1338
|
-
if (channelKey ===
|
|
1339
|
-
if (
|
|
1464
|
+
const channelKey = msg._channelKey || msg.module || 'product';
|
|
1465
|
+
if (channelKey === 'product') {
|
|
1466
|
+
if (msg.relation_product_ids?.length) {
|
|
1340
1467
|
sseRefreshIds.push(...msg.relation_product_ids);
|
|
1341
1468
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
else if (msg.id)
|
|
1346
|
-
deleteIds.push(msg.id);
|
|
1469
|
+
// 1. 删除场景:operation === 'delete' 或 action === 'delete'
|
|
1470
|
+
if (msg.operation === 'delete' || msg.action === 'delete') {
|
|
1471
|
+
if (msg.ids?.length) deleteIds.push(...msg.ids);else if (msg.id) deleteIds.push(msg.id);
|
|
1347
1472
|
continue;
|
|
1348
1473
|
}
|
|
1349
|
-
|
|
1474
|
+
|
|
1475
|
+
// 2. change_types 包含 price → 仅收集需要刷新价格的 IDs(不走 SSE 拉取)
|
|
1476
|
+
if (msg.change_types?.includes('price')) {
|
|
1350
1477
|
const ids = msg.ids || (msg.id ? [msg.id] : []);
|
|
1351
1478
|
priceRefreshIds.push(...ids);
|
|
1352
1479
|
if (msg.body) {
|
|
1353
1480
|
const bodyId = msg.body.id;
|
|
1354
|
-
if (bodyId)
|
|
1355
|
-
bodyUpdates.set(bodyId, msg.body);
|
|
1481
|
+
if (bodyId) bodyUpdates.set(bodyId, msg.body);
|
|
1356
1482
|
}
|
|
1357
1483
|
continue;
|
|
1358
1484
|
}
|
|
1485
|
+
|
|
1486
|
+
// 3. 有 body → 完整数据直接覆盖本地
|
|
1359
1487
|
if (msg.body) {
|
|
1360
1488
|
const bodyId = msg.body.id || msg.id;
|
|
1361
|
-
if (bodyId)
|
|
1362
|
-
bodyUpdates.set(bodyId, msg.body);
|
|
1489
|
+
if (bodyId) bodyUpdates.set(bodyId, msg.body);
|
|
1363
1490
|
continue;
|
|
1364
1491
|
}
|
|
1365
|
-
|
|
1492
|
+
|
|
1493
|
+
// 4. 其他情况(有 ids 无 body 无 change_types)→ SSE 拉取
|
|
1494
|
+
if (msg.ids?.length) {
|
|
1366
1495
|
sseRefreshIds.push(...msg.ids);
|
|
1367
1496
|
} else if (msg.id) {
|
|
1368
1497
|
sseRefreshIds.push(msg.id);
|
|
1369
1498
|
}
|
|
1370
|
-
} else if (channelKey ===
|
|
1499
|
+
} else if (channelKey === 'product_quotation') {
|
|
1500
|
+
// 报价单变更影响范围大,标记清除全量价格缓存
|
|
1371
1501
|
shouldClearPriceCache = true;
|
|
1372
|
-
if (
|
|
1502
|
+
if (msg.relation_product_ids?.length) {
|
|
1373
1503
|
sseRefreshIds.push(...msg.relation_product_ids);
|
|
1374
1504
|
}
|
|
1375
|
-
} else if ([
|
|
1376
|
-
if (
|
|
1505
|
+
} else if (['product_collection', 'product_category'].includes(channelKey)) {
|
|
1506
|
+
if (msg.relation_product_ids?.length) {
|
|
1377
1507
|
sseRefreshIds.push(...msg.relation_product_ids);
|
|
1378
1508
|
}
|
|
1379
|
-
} else if ([
|
|
1380
|
-
if (
|
|
1509
|
+
} else if (['product_data_variant'].includes(channelKey)) {
|
|
1510
|
+
if (msg.product_ids?.length) {
|
|
1381
1511
|
sseRefreshIds.push(...msg.product_ids);
|
|
1382
1512
|
}
|
|
1383
1513
|
}
|
|
@@ -1385,12 +1515,18 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1385
1515
|
const uniqueDeleteIds = [...new Set(deleteIds)];
|
|
1386
1516
|
const uniqueSSEIds = [...new Set(sseRefreshIds)];
|
|
1387
1517
|
const uniquePriceIds = [...new Set(priceRefreshIds)];
|
|
1518
|
+
|
|
1519
|
+
// 1. 处理删除
|
|
1388
1520
|
if (uniqueDeleteIds.length > 0) {
|
|
1389
1521
|
await this.removeProductsByIds(uniqueDeleteIds);
|
|
1390
1522
|
}
|
|
1391
|
-
|
|
1523
|
+
|
|
1524
|
+
/** 本批次待 patch 的商品(按 id 去重,SSE 覆盖 body) */
|
|
1525
|
+
const patchProductMap = new Map();
|
|
1392
1526
|
const patchMergeActions = {};
|
|
1393
1527
|
let storeMutated = uniqueDeleteIds.length > 0;
|
|
1528
|
+
|
|
1529
|
+
// 2. 处理 body 直接覆盖(仅内存,IndexDB 批次末统一 patch)
|
|
1394
1530
|
if (bodyUpdates.size > 0) {
|
|
1395
1531
|
const bodyResult = await this.applyBodyUpdatesToStore(bodyUpdates, {
|
|
1396
1532
|
skipIndexDB: true,
|
|
@@ -1402,6 +1538,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1402
1538
|
}
|
|
1403
1539
|
storeMutated = true;
|
|
1404
1540
|
}
|
|
1541
|
+
|
|
1542
|
+
// 3. 处理 SSE 增量拉取(仅内存,IndexDB 批次末统一 patch)
|
|
1405
1543
|
let freshProducts = [];
|
|
1406
1544
|
if (uniqueSSEIds.length > 0) {
|
|
1407
1545
|
freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
|
|
@@ -1416,28 +1554,22 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1416
1554
|
}
|
|
1417
1555
|
storeMutated = true;
|
|
1418
1556
|
}
|
|
1419
|
-
this.logInfo(
|
|
1557
|
+
this.logInfo('processProductSyncMessages: SSE 增量更新完成123', {
|
|
1420
1558
|
requestedCount: uniqueSSEIds.length,
|
|
1421
1559
|
receivedCount: freshProducts.length
|
|
1422
1560
|
});
|
|
1423
1561
|
}
|
|
1424
1562
|
const patchProducts = [...patchProductMap.values()];
|
|
1425
1563
|
if (patchProducts.length > 0) {
|
|
1426
|
-
await this.patchProductsInIndexDB(
|
|
1427
|
-
patchProducts,
|
|
1428
|
-
"pubsub.processProductSyncMessages",
|
|
1429
|
-
patchMergeActions
|
|
1430
|
-
);
|
|
1564
|
+
await this.patchProductsInIndexDB(patchProducts, 'pubsub.processProductSyncMessages', patchMergeActions);
|
|
1431
1565
|
}
|
|
1432
1566
|
if (storeMutated) {
|
|
1433
|
-
this.core.effects.emit(
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
])];
|
|
1440
|
-
this.logInfo("processProductSyncMessages: 处理完成123", {
|
|
1567
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// 收集所有非 delete 的变更 IDs(body + SSE + price),用于增量更新价格缓存
|
|
1571
|
+
const allChangedIds = [...new Set([...Array.from(bodyUpdates.keys()), ...uniqueSSEIds, ...uniquePriceIds])];
|
|
1572
|
+
this.logInfo('processProductSyncMessages: 处理完成123', {
|
|
1441
1573
|
deleteCount: uniqueDeleteIds.length,
|
|
1442
1574
|
bodyUpdateCount: bodyUpdates.size,
|
|
1443
1575
|
sseRefreshCount: uniqueSSEIds.length,
|
|
@@ -1447,40 +1579,55 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1447
1579
|
});
|
|
1448
1580
|
const hasChanges = uniqueDeleteIds.length > 0 || allChangedIds.length > 0 || shouldClearPriceCache;
|
|
1449
1581
|
if (!hasChanges) {
|
|
1450
|
-
this.logInfo(
|
|
1582
|
+
this.logInfo('processProductSyncMessages: 没有变更,不触发 onProductsSyncCompleted');
|
|
1451
1583
|
return;
|
|
1452
1584
|
}
|
|
1585
|
+
|
|
1586
|
+
// 报价单变更 → 清除全量价格缓存,后续走全量重建
|
|
1453
1587
|
if (shouldClearPriceCache) {
|
|
1454
1588
|
try {
|
|
1455
1589
|
await this.refreshQuotationPriceBridge();
|
|
1456
1590
|
} catch (error) {
|
|
1457
1591
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1458
|
-
this.logError(
|
|
1592
|
+
this.logError('报价单桥接刷新失败,将继续清理价格缓存', {
|
|
1593
|
+
error: errorMessage
|
|
1594
|
+
});
|
|
1459
1595
|
}
|
|
1460
1596
|
this.clearPriceCache();
|
|
1461
1597
|
}
|
|
1462
|
-
await this.core.effects.emit(
|
|
1598
|
+
await this.core.effects.emit(_types.ProductsHooks.onProductsSyncCompleted, {
|
|
1463
1599
|
changedIds: allChangedIds
|
|
1464
1600
|
});
|
|
1465
1601
|
}
|
|
1602
|
+
|
|
1466
1603
|
/**
|
|
1467
1604
|
* 通过 SSE 按 ids 增量拉取商品数据
|
|
1468
1605
|
* 请求 GET /shop/core/stream?type=product&ids={ids}
|
|
1469
1606
|
*/
|
|
1470
1607
|
async fetchProductsBySSE(ids) {
|
|
1471
1608
|
if (!this.productDataSource) {
|
|
1472
|
-
this.logWarning(
|
|
1609
|
+
this.logWarning('fetchProductsBySSE: ProductDataSource 不可用');
|
|
1473
1610
|
return [];
|
|
1474
1611
|
}
|
|
1475
|
-
this.logInfo(
|
|
1612
|
+
this.logInfo('fetchProductsBySSE: 开始', {
|
|
1613
|
+
ids,
|
|
1614
|
+
count: ids.length
|
|
1615
|
+
});
|
|
1476
1616
|
const t0 = performance.now();
|
|
1477
1617
|
try {
|
|
1478
1618
|
const productList = await this.productDataSource.run({
|
|
1479
|
-
sse: {
|
|
1619
|
+
sse: {
|
|
1620
|
+
query: {
|
|
1621
|
+
type: 'product',
|
|
1622
|
+
ids
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1480
1625
|
});
|
|
1481
1626
|
const list = productList || [];
|
|
1482
|
-
(0,
|
|
1483
|
-
|
|
1627
|
+
(0, _product.perfMark)('fetchProductsBySSE', performance.now() - t0, {
|
|
1628
|
+
count: list.length
|
|
1629
|
+
});
|
|
1630
|
+
this.logInfo('fetchProductsBySSE: 成功', {
|
|
1484
1631
|
requestedCount: ids.length,
|
|
1485
1632
|
receivedCount: list.length,
|
|
1486
1633
|
duration: `${Math.round(performance.now() - t0)}ms`
|
|
@@ -1488,10 +1635,14 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1488
1635
|
return list;
|
|
1489
1636
|
} catch (error) {
|
|
1490
1637
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1491
|
-
this.logError(
|
|
1638
|
+
this.logError('fetchProductsBySSE: 失败', {
|
|
1639
|
+
ids,
|
|
1640
|
+
error: errorMessage
|
|
1641
|
+
});
|
|
1492
1642
|
return [];
|
|
1493
1643
|
}
|
|
1494
1644
|
}
|
|
1645
|
+
|
|
1495
1646
|
/**
|
|
1496
1647
|
* 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
|
|
1497
1648
|
* 已存在的 → 直接替换;不存在的 → 追加
|
|
@@ -1501,17 +1652,19 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1501
1652
|
*/
|
|
1502
1653
|
async applyBodyUpdatesToStore(bodyUpdates, options) {
|
|
1503
1654
|
const changedProductIds = [...bodyUpdates.keys()];
|
|
1504
|
-
this.logInfo(
|
|
1655
|
+
this.logInfo('applyBodyUpdatesToStore: 开始', {
|
|
1656
|
+
count: bodyUpdates.size
|
|
1657
|
+
});
|
|
1505
1658
|
let updatedCount = 0;
|
|
1506
1659
|
let newCount = 0;
|
|
1507
|
-
const appliedIds =
|
|
1660
|
+
const appliedIds = new Set();
|
|
1508
1661
|
const mergeActions = {};
|
|
1509
1662
|
const changedProducts = [];
|
|
1510
|
-
this.store.list = this.store.list.map(
|
|
1663
|
+
this.store.list = this.store.list.map(p => {
|
|
1511
1664
|
if (bodyUpdates.has(p.id)) {
|
|
1512
1665
|
updatedCount++;
|
|
1513
1666
|
appliedIds.add(p.id);
|
|
1514
|
-
mergeActions[p.id] =
|
|
1667
|
+
mergeActions[p.id] = 'update';
|
|
1515
1668
|
const next = bodyUpdates.get(p.id);
|
|
1516
1669
|
changedProducts.push(next);
|
|
1517
1670
|
return next;
|
|
@@ -1522,24 +1675,30 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1522
1675
|
if (!appliedIds.has(id)) {
|
|
1523
1676
|
this.store.list.push(body);
|
|
1524
1677
|
newCount++;
|
|
1525
|
-
mergeActions[id] =
|
|
1678
|
+
mergeActions[id] = 'insert';
|
|
1526
1679
|
changedProducts.push(body);
|
|
1527
1680
|
}
|
|
1528
1681
|
}
|
|
1529
1682
|
this.syncProductsMap();
|
|
1530
|
-
if (!
|
|
1531
|
-
await this.patchProductsInIndexDB(changedProducts,
|
|
1683
|
+
if (!options?.skipIndexDB) {
|
|
1684
|
+
await this.patchProductsInIndexDB(changedProducts, 'pubsub.bodyUpdate', mergeActions);
|
|
1532
1685
|
}
|
|
1533
|
-
if (!
|
|
1534
|
-
this.core.effects.emit(
|
|
1686
|
+
if (!options?.skipEmit) {
|
|
1687
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1535
1688
|
}
|
|
1536
|
-
this.logInfo(
|
|
1689
|
+
this.logInfo('applyBodyUpdatesToStore: 完成', {
|
|
1537
1690
|
updatedCount,
|
|
1538
1691
|
newCount,
|
|
1539
1692
|
totalCount: this.store.list.length
|
|
1540
1693
|
});
|
|
1541
|
-
return {
|
|
1694
|
+
return {
|
|
1695
|
+
changedProducts,
|
|
1696
|
+
mergeActions,
|
|
1697
|
+
updatedCount,
|
|
1698
|
+
newCount
|
|
1699
|
+
};
|
|
1542
1700
|
}
|
|
1701
|
+
|
|
1543
1702
|
/**
|
|
1544
1703
|
* 将增量拉取的商品合并到 store
|
|
1545
1704
|
* 已存在的 → 替换;新的 → 追加
|
|
@@ -1548,100 +1707,129 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1548
1707
|
* await this.mergeProductsToStore(freshProducts, { skipIndexDB: true })
|
|
1549
1708
|
*/
|
|
1550
1709
|
async mergeProductsToStore(freshProducts, options) {
|
|
1551
|
-
const freshMap =
|
|
1710
|
+
const freshMap = new Map();
|
|
1552
1711
|
for (const p of freshProducts) {
|
|
1553
1712
|
freshMap.set(p.id, p);
|
|
1554
1713
|
}
|
|
1555
1714
|
const mergeActions = {};
|
|
1556
1715
|
const changedProducts = [];
|
|
1557
|
-
const updatedList = this.store.list.map(
|
|
1716
|
+
const updatedList = this.store.list.map(p => {
|
|
1558
1717
|
if (freshMap.has(p.id)) {
|
|
1559
1718
|
const fresh = freshMap.get(p.id);
|
|
1560
1719
|
freshMap.delete(p.id);
|
|
1561
|
-
mergeActions[p.id] =
|
|
1720
|
+
mergeActions[p.id] = 'update';
|
|
1562
1721
|
changedProducts.push(fresh);
|
|
1563
1722
|
return fresh;
|
|
1564
1723
|
}
|
|
1565
1724
|
return p;
|
|
1566
1725
|
});
|
|
1726
|
+
|
|
1727
|
+
// freshMap 中剩余的是新商品(create 场景)
|
|
1567
1728
|
const newCount = freshMap.size;
|
|
1568
1729
|
for (const p of freshMap.values()) {
|
|
1569
1730
|
updatedList.push(p);
|
|
1570
|
-
mergeActions[p.id] =
|
|
1731
|
+
mergeActions[p.id] = 'insert';
|
|
1571
1732
|
changedProducts.push(p);
|
|
1572
1733
|
}
|
|
1573
1734
|
const updatedCount = freshProducts.length - newCount;
|
|
1574
|
-
const changedProductIds = freshProducts.map(
|
|
1735
|
+
const changedProductIds = freshProducts.map(p => p.id);
|
|
1575
1736
|
this.store.list = updatedList;
|
|
1576
1737
|
this.syncProductsMap();
|
|
1577
|
-
if (!
|
|
1578
|
-
await this.patchProductsInIndexDB(changedProducts,
|
|
1738
|
+
if (!options?.skipIndexDB) {
|
|
1739
|
+
await this.patchProductsInIndexDB(changedProducts, 'pubsub.mergeProductsToStore', mergeActions);
|
|
1579
1740
|
}
|
|
1580
|
-
if (!
|
|
1581
|
-
this.core.effects.emit(
|
|
1741
|
+
if (!options?.skipEmit) {
|
|
1742
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1582
1743
|
}
|
|
1583
|
-
this.logInfo(
|
|
1744
|
+
this.logInfo('mergeProductsToStore: 合并完成', {
|
|
1584
1745
|
updatedCount,
|
|
1585
1746
|
newCount,
|
|
1586
1747
|
totalCount: this.store.list.length
|
|
1587
1748
|
});
|
|
1588
|
-
return {
|
|
1749
|
+
return {
|
|
1750
|
+
changedProducts,
|
|
1751
|
+
mergeActions,
|
|
1752
|
+
updatedCount,
|
|
1753
|
+
newCount
|
|
1754
|
+
};
|
|
1589
1755
|
}
|
|
1756
|
+
|
|
1590
1757
|
/**
|
|
1591
1758
|
* 静默全量刷新:后台重新拉取全量 SSE 数据并更新本地
|
|
1592
1759
|
* 拿到完整数据后一次性替换 store,清除价格缓存,触发 onProductsSyncCompleted
|
|
1593
1760
|
* @returns 刷新后的商品列表
|
|
1594
1761
|
*/
|
|
1595
|
-
async silentRefresh() {
|
|
1762
|
+
async silentRefresh(options = {}) {
|
|
1596
1763
|
const t0 = performance.now();
|
|
1597
1764
|
const now = Date.now();
|
|
1598
|
-
const
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1765
|
+
const trigger = options.trigger || 'background';
|
|
1766
|
+
const elapsedSincePreloadMs = now - this.lastPreloadCompletedAtMs;
|
|
1767
|
+
const elapsedSinceFullFetchMs = now - this.lastServerFullFetchAtMs;
|
|
1768
|
+
if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreloadMs < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetchMs < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
|
|
1769
|
+
this.logInfo('silentRefresh 跳过全量 SSE', {
|
|
1770
|
+
trigger,
|
|
1771
|
+
reason: 'within_min_interval',
|
|
1772
|
+
minIntervalMs: PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS,
|
|
1773
|
+
elapsedSincePreloadMs,
|
|
1774
|
+
elapsedSinceFullFetchMs,
|
|
1775
|
+
productCount: this.store.list.length
|
|
1776
|
+
});
|
|
1601
1777
|
return this.store.list;
|
|
1602
1778
|
}
|
|
1603
|
-
this.logInfo(
|
|
1779
|
+
this.logInfo('silentRefresh 开始', {
|
|
1780
|
+
trigger,
|
|
1781
|
+
elapsedSincePreloadMs,
|
|
1782
|
+
elapsedSinceFullFetchMs,
|
|
1783
|
+
productCount: this.store.list.length
|
|
1784
|
+
});
|
|
1604
1785
|
try {
|
|
1605
|
-
const products = await this.loadProductsByServer(
|
|
1786
|
+
const products = await this.loadProductsByServer({
|
|
1787
|
+
trigger
|
|
1788
|
+
});
|
|
1606
1789
|
if (products && products.length > 0) {
|
|
1607
1790
|
this.store.list = products;
|
|
1608
1791
|
this.syncProductsMap();
|
|
1609
1792
|
this.clearPriceCache();
|
|
1610
|
-
this.core.effects.emit(
|
|
1611
|
-
await this.core.effects.emit(
|
|
1612
|
-
this.logInfo(
|
|
1793
|
+
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1794
|
+
await this.core.effects.emit(_types.ProductsHooks.onProductsSyncCompleted, null);
|
|
1795
|
+
this.logInfo('silentRefresh 完成', {
|
|
1796
|
+
trigger,
|
|
1613
1797
|
productCount: products.length,
|
|
1614
1798
|
duration: `${Math.round(performance.now() - t0)}ms`
|
|
1615
1799
|
});
|
|
1616
1800
|
} else {
|
|
1617
|
-
this.logWarning(
|
|
1801
|
+
this.logWarning('silentRefresh: 服务器未返回数据');
|
|
1618
1802
|
}
|
|
1619
|
-
(0,
|
|
1803
|
+
(0, _product.perfMark)('silentRefresh', performance.now() - t0, {
|
|
1804
|
+
count: this.store.list.length
|
|
1805
|
+
});
|
|
1620
1806
|
return this.store.list;
|
|
1621
1807
|
} catch (error) {
|
|
1622
1808
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1623
|
-
this.logError(
|
|
1809
|
+
this.logError('silentRefresh 失败', {
|
|
1810
|
+
trigger,
|
|
1624
1811
|
duration: `${Math.round(performance.now() - t0)}ms`,
|
|
1625
1812
|
error: errorMessage
|
|
1626
1813
|
});
|
|
1627
1814
|
return this.store.list;
|
|
1628
1815
|
}
|
|
1629
1816
|
}
|
|
1817
|
+
|
|
1630
1818
|
/**
|
|
1631
1819
|
* 销毁同步资源(取消 pubsub 订阅、清除定时器)
|
|
1632
1820
|
*/
|
|
1633
1821
|
destroyProductSync() {
|
|
1634
|
-
var _a;
|
|
1635
1822
|
if (this.syncTimer) {
|
|
1636
1823
|
clearTimeout(this.syncTimer);
|
|
1637
|
-
this.syncTimer =
|
|
1824
|
+
this.syncTimer = undefined;
|
|
1638
1825
|
}
|
|
1639
|
-
if (
|
|
1826
|
+
if (this.productDataSource?.destroy) {
|
|
1640
1827
|
this.productDataSource.destroy();
|
|
1641
1828
|
}
|
|
1642
1829
|
this.pendingSyncMessages = [];
|
|
1643
|
-
this.logInfo(
|
|
1830
|
+
this.logInfo('destroyProductSync: 同步资源已销毁');
|
|
1644
1831
|
}
|
|
1832
|
+
|
|
1645
1833
|
/**
|
|
1646
1834
|
* 获取模块的路由定义
|
|
1647
1835
|
* Products 模块暂不提供路由,由 Server 层统一处理
|
|
@@ -1649,8 +1837,5 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
|
|
|
1649
1837
|
getRoutes() {
|
|
1650
1838
|
return [];
|
|
1651
1839
|
}
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
0 && (module.exports = {
|
|
1655
|
-
ProductsModule
|
|
1656
|
-
});
|
|
1840
|
+
}
|
|
1841
|
+
exports.ProductsModule = ProductsModule;
|