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