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