@pisell/pisellos 2.2.291 → 2.2.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +108 -65
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
- package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/dist/model/strategy/adapter/itemRule/type.js +19 -1
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/BookingContext/index.d.ts +3 -0
- package/dist/modules/BookingContext/index.js +42 -13
- package/dist/modules/BookingContext/types.d.ts +4 -4
- package/dist/modules/BookingContext/types.js +3 -3
- package/dist/modules/OpenData/types.d.ts +1 -0
- package/dist/modules/Order/index.d.ts +18 -3
- package/dist/modules/Order/index.js +885 -485
- package/dist/modules/Order/salesNotes.d.ts +31 -0
- package/dist/modules/Order/salesNotes.js +492 -0
- package/dist/modules/Order/types.d.ts +93 -5
- package/dist/modules/Order/types.js +5 -0
- package/dist/modules/Order/utils.d.ts +12 -0
- package/dist/modules/Order/utils.js +96 -31
- package/dist/modules/Product/types.d.ts +2 -0
- package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
- package/dist/modules/ProductList/clientDataVariants.js +216 -0
- package/dist/modules/ProductList/index.d.ts +18 -3
- package/dist/modules/ProductList/index.js +220 -38
- package/dist/modules/ProductList/types.d.ts +10 -0
- package/dist/modules/Quotation/index.d.ts +22 -0
- package/dist/modules/Quotation/index.js +293 -28
- package/dist/modules/ResourcePlanner/index.d.ts +0 -22
- package/dist/modules/ResourcePlanner/index.js +1 -180
- package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
- package/dist/modules/ResourcePlanner/localClock.js +183 -0
- package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
- package/dist/modules/ResourcePlanner/planner.js +2111 -958
- package/dist/modules/ResourcePlanner/types.d.ts +359 -187
- package/dist/modules/ResourcePlanner/types.js +33 -1
- package/dist/plugins/request.d.ts +1 -0
- package/dist/server/index.d.ts +17 -2
- package/dist/server/index.js +1134 -932
- package/dist/server/modules/floor-plan/index.d.ts +4 -0
- package/dist/server/modules/floor-plan/index.js +240 -98
- package/dist/server/modules/menu/index.js +48 -23
- package/dist/server/modules/order/index.d.ts +17 -7
- package/dist/server/modules/order/index.js +419 -207
- package/dist/server/modules/products/index.d.ts +10 -4
- package/dist/server/modules/products/index.js +177 -84
- package/dist/server/modules/resource/index.js +21 -13
- package/dist/server/utils/product.d.ts +1 -1
- package/dist/server/utils/product.js +186 -22
- package/dist/server/utils/small-ticket.d.ts +9 -1
- package/dist/server/utils/small-ticket.js +131 -84
- package/dist/solution/BaseSales/index.d.ts +26 -3
- package/dist/solution/BaseSales/index.js +1384 -905
- package/dist/solution/BaseSales/types.d.ts +2 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +10 -3
- package/dist/solution/BaseSales/utils.js +6 -3
- package/dist/solution/BookingByStep/index.d.ts +4 -4
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.js +4 -2
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -3
- package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
- package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
- package/dist/solution/RegisterAndLogin/config.js +95 -40
- package/dist/solution/RegisterAndLogin/index.js +4 -1
- package/dist/solution/ScanOrder/index.d.ts +1 -0
- package/dist/solution/ScanOrder/index.js +31 -27
- package/dist/solution/ScanOrder/types.d.ts +2 -0
- package/dist/solution/ScanOrder/utils.d.ts +1 -0
- package/dist/solution/ScanOrder/utils.js +8 -3
- package/dist/solution/UnifiedBookingSales/index.d.ts +128 -74
- package/dist/solution/UnifiedBookingSales/index.js +3877 -963
- package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
- package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
- package/dist/solution/UnifiedBookingSales/types.d.ts +161 -77
- package/dist/solution/UnifiedBookingSales/types.js +4 -9
- package/dist/solution/VenueBooking/index.d.ts +3 -0
- package/dist/solution/VenueBooking/index.js +549 -497
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
- package/dist/utils/platform.d.ts +8 -0
- package/dist/utils/platform.js +15 -0
- package/lib/core/index.d.ts +7 -0
- package/lib/core/index.js +20 -2
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
- package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
- package/lib/model/strategy/adapter/itemRule/type.js +19 -1
- package/lib/modules/BookingContext/index.d.ts +3 -0
- package/lib/modules/BookingContext/index.js +35 -8
- package/lib/modules/BookingContext/types.d.ts +4 -4
- package/lib/modules/BookingContext/types.js +3 -3
- package/lib/modules/OpenData/types.d.ts +1 -0
- package/lib/modules/Order/index.d.ts +18 -3
- package/lib/modules/Order/index.js +325 -28
- package/lib/modules/Order/salesNotes.d.ts +31 -0
- package/lib/modules/Order/salesNotes.js +382 -0
- package/lib/modules/Order/types.d.ts +93 -5
- package/lib/modules/Order/types.js +4 -0
- package/lib/modules/Order/utils.d.ts +12 -0
- package/lib/modules/Order/utils.js +85 -16
- package/lib/modules/Product/types.d.ts +2 -0
- package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
- package/lib/modules/ProductList/clientDataVariants.js +204 -0
- package/lib/modules/ProductList/index.d.ts +18 -3
- package/lib/modules/ProductList/index.js +137 -8
- package/lib/modules/ProductList/types.d.ts +10 -0
- package/lib/modules/Quotation/index.d.ts +22 -0
- package/lib/modules/Quotation/index.js +158 -11
- package/lib/modules/ResourcePlanner/index.d.ts +0 -22
- package/lib/modules/ResourcePlanner/index.js +9 -129
- package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
- package/lib/modules/ResourcePlanner/localClock.js +205 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
- package/lib/modules/ResourcePlanner/planner.js +1807 -783
- package/lib/modules/ResourcePlanner/types.d.ts +359 -187
- package/lib/modules/ResourcePlanner/types.js +13 -1
- package/lib/plugins/request.d.ts +1 -0
- package/lib/server/index.d.ts +17 -2
- package/lib/server/index.js +149 -21
- package/lib/server/modules/floor-plan/index.d.ts +4 -0
- package/lib/server/modules/floor-plan/index.js +60 -7
- package/lib/server/modules/menu/index.js +26 -4
- package/lib/server/modules/order/index.d.ts +17 -7
- package/lib/server/modules/order/index.js +194 -57
- package/lib/server/modules/products/index.d.ts +10 -4
- package/lib/server/modules/products/index.js +93 -23
- package/lib/server/modules/resource/index.js +12 -2
- package/lib/server/utils/product.d.ts +1 -1
- package/lib/server/utils/product.js +163 -3
- package/lib/server/utils/small-ticket.d.ts +9 -1
- package/lib/server/utils/small-ticket.js +123 -79
- package/lib/solution/BaseSales/index.d.ts +26 -3
- package/lib/solution/BaseSales/index.js +396 -70
- package/lib/solution/BaseSales/types.d.ts +2 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -2
- package/lib/solution/BaseSales/utils.js +3 -0
- package/lib/solution/BookingByStep/index.d.ts +4 -4
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.js +2 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.js +5 -2
- package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
- package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
- package/lib/solution/RegisterAndLogin/config.js +49 -8
- package/lib/solution/RegisterAndLogin/index.js +4 -1
- package/lib/solution/ScanOrder/index.d.ts +1 -0
- package/lib/solution/ScanOrder/index.js +5 -1
- package/lib/solution/ScanOrder/types.d.ts +2 -0
- package/lib/solution/ScanOrder/utils.d.ts +1 -0
- package/lib/solution/ScanOrder/utils.js +4 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +128 -74
- package/lib/solution/UnifiedBookingSales/index.js +2592 -424
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +316 -0
- package/lib/solution/UnifiedBookingSales/types.d.ts +161 -77
- package/lib/solution/UnifiedBookingSales/types.js +4 -6
- package/lib/solution/VenueBooking/index.d.ts +3 -0
- package/lib/solution/VenueBooking/index.js +33 -9
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
- package/lib/utils/platform.d.ts +8 -0
- package/lib/utils/platform.js +22 -0
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
30
30
|
logger; // LoggerManager 实例
|
|
31
31
|
otherParams = {};
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// 商品格式化缓存:Map<查询上下文 key, 已应用价格与多语言的商品列表>
|
|
34
34
|
productsPriceCache = new Map();
|
|
35
35
|
|
|
36
36
|
// 缓存配置
|
|
@@ -50,6 +50,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
50
50
|
quotationBridgeLoadedKey = '';
|
|
51
51
|
quotationBridgeRefreshPromise;
|
|
52
52
|
quotationBridgeRefreshKey;
|
|
53
|
+
quotationBridgeUpdatedCallback;
|
|
53
54
|
quotationScheduleCache = new Map();
|
|
54
55
|
quotationScheduleCacheSource = null;
|
|
55
56
|
quotationScheduleCacheLoaded = false;
|
|
@@ -252,6 +253,14 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
252
253
|
return schedules[0];
|
|
253
254
|
});
|
|
254
255
|
}
|
|
256
|
+
if (params.onQuotationUpdated) {
|
|
257
|
+
this.quotationBridgeUpdatedCallback = params.onQuotationUpdated;
|
|
258
|
+
}
|
|
259
|
+
this.quotationPriceBridge.setQuotationListUpdatedListener?.(() => {
|
|
260
|
+
this.quotationBridgeLoadedKey = this.getQuotationBridgeScopeKey();
|
|
261
|
+
this.clearPriceCache();
|
|
262
|
+
this.quotationBridgeUpdatedCallback?.();
|
|
263
|
+
});
|
|
255
264
|
await this.refreshQuotationPriceBridge({
|
|
256
265
|
customer_id: params.customer_id
|
|
257
266
|
});
|
|
@@ -385,8 +394,8 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
385
394
|
|
|
386
395
|
/**
|
|
387
396
|
* 获取应用了价格的商品列表(带缓存)
|
|
388
|
-
*
|
|
389
|
-
*
|
|
397
|
+
* 使用时间、客户、策略、商品范围和语言作为缓存 key
|
|
398
|
+
* 缓存的是已经应用了价格与多语言的完整商品列表,避免重复转换
|
|
390
399
|
* @param schedule_date 日期
|
|
391
400
|
* @param extraContext 额外的上下文数据(可选,由 Server 层传入)
|
|
392
401
|
* @param options 可选参数
|
|
@@ -591,9 +600,10 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
591
600
|
}
|
|
592
601
|
getProductsPriceCacheKey(schedule_date, extraContext, productIds) {
|
|
593
602
|
const datetime = extraContext?.schedule_datetime || schedule_date;
|
|
603
|
+
const locale = extraContext?.locale ?? this.core?.context?.locale ?? '';
|
|
594
604
|
const productScope = productIds ? `:products:${productIds.join(',')}` : '';
|
|
595
605
|
const strategyScope = this.stringifyStable(extraContext?.strategy_context || {});
|
|
596
|
-
return `${datetime}:customer:${extraContext?.customer_id ?? ''}
|
|
606
|
+
return `${datetime}:locale:${locale}:customer:${extraContext?.customer_id ?? ''}` + `:strategy:${strategyScope}${productScope}`;
|
|
597
607
|
}
|
|
598
608
|
|
|
599
609
|
/**
|
|
@@ -822,17 +832,22 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
822
832
|
*/
|
|
823
833
|
clearPriceCache() {
|
|
824
834
|
this.productsPriceCache.clear();
|
|
835
|
+
this.quotationScheduleCache.clear();
|
|
836
|
+
this.quotationScheduleCacheLoaded = false;
|
|
825
837
|
}
|
|
826
838
|
|
|
827
839
|
/**
|
|
828
840
|
* 通过 ProductDataSource SSE 加载完整商品列表
|
|
829
841
|
*/
|
|
830
|
-
async loadProductsByServer() {
|
|
842
|
+
async loadProductsByServer(options = {}) {
|
|
831
843
|
if (!this.productDataSource) {
|
|
832
844
|
this.logWarning('loadProductsByServer: ProductDataSource 不可用');
|
|
833
845
|
return [];
|
|
834
846
|
}
|
|
835
|
-
|
|
847
|
+
const trigger = options.trigger || 'unknown';
|
|
848
|
+
this.logInfo('开始通过 DataSource SSE 加载商品列表', {
|
|
849
|
+
trigger
|
|
850
|
+
});
|
|
836
851
|
const t0 = performance.now();
|
|
837
852
|
try {
|
|
838
853
|
const tSSE = performance.now();
|
|
@@ -844,6 +859,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
844
859
|
count: list.length
|
|
845
860
|
});
|
|
846
861
|
this.logInfo('通过 DataSource SSE 加载商品列表成功', {
|
|
862
|
+
trigger,
|
|
847
863
|
productCount: list.length,
|
|
848
864
|
duration: `${Math.round(performance.now() - t0)}ms`
|
|
849
865
|
});
|
|
@@ -859,6 +875,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
859
875
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
860
876
|
console.error('[Products] 加载商品数据失败:', error);
|
|
861
877
|
this.logError('通过 DataSource SSE 加载商品列表失败', {
|
|
878
|
+
trigger,
|
|
862
879
|
duration: `${duration}ms`,
|
|
863
880
|
error: errorMessage
|
|
864
881
|
});
|
|
@@ -1044,7 +1061,9 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1044
1061
|
async refreshProducts() {
|
|
1045
1062
|
const tTotal = performance.now();
|
|
1046
1063
|
this.logInfo('refreshProducts 开始');
|
|
1047
|
-
const products = await this.loadProductsByServer(
|
|
1064
|
+
const products = await this.loadProductsByServer({
|
|
1065
|
+
trigger: 'product_sync'
|
|
1066
|
+
});
|
|
1048
1067
|
if (products && products.length > 0) {
|
|
1049
1068
|
// 服务器返回的数据已经是全新的,无需 cloneDeep
|
|
1050
1069
|
this.store.list = products;
|
|
@@ -1098,20 +1117,23 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1098
1117
|
this.logWarning('loadProductsFromIndexDB: dbManager 不可用');
|
|
1099
1118
|
return [];
|
|
1100
1119
|
}
|
|
1120
|
+
const startedAt = performance.now();
|
|
1101
1121
|
try {
|
|
1102
|
-
const t0 = performance.now();
|
|
1103
1122
|
const products = await this.dbManager.getAll(INDEXDB_STORE_NAME);
|
|
1104
|
-
|
|
1123
|
+
const durationMs = +(performance.now() - startedAt).toFixed(2);
|
|
1124
|
+
(0, _product.perfMark)('loadProductsFromIndexDB', durationMs, {
|
|
1105
1125
|
count: products?.length ?? 0
|
|
1106
1126
|
});
|
|
1107
1127
|
this.logInfo('从 IndexDB 加载商品数据', {
|
|
1108
|
-
productCount: products?.length ?? 0
|
|
1128
|
+
productCount: products?.length ?? 0,
|
|
1129
|
+
durationMs
|
|
1109
1130
|
});
|
|
1110
1131
|
return products || [];
|
|
1111
1132
|
} catch (error) {
|
|
1112
1133
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1113
1134
|
console.error('[Products] 从 IndexDB 读取数据失败:', error);
|
|
1114
1135
|
this.logError('从 IndexDB 读取数据失败', {
|
|
1136
|
+
durationMs: +(performance.now() - startedAt).toFixed(2),
|
|
1115
1137
|
error: errorMessage
|
|
1116
1138
|
});
|
|
1117
1139
|
return [];
|
|
@@ -1143,23 +1165,45 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1143
1165
|
this.logWarning('replaceProductsSnapshotInIndexDB: dbManager 不可用');
|
|
1144
1166
|
return;
|
|
1145
1167
|
}
|
|
1168
|
+
const enqueuedAt = performance.now();
|
|
1169
|
+
let queueWaitMs = 0;
|
|
1170
|
+
let clearDurationMs = 0;
|
|
1171
|
+
let writeDurationMs = 0;
|
|
1146
1172
|
try {
|
|
1147
1173
|
await this.runInProductIndexDBSaveQueue(async () => {
|
|
1174
|
+
const operationStartedAt = performance.now();
|
|
1175
|
+
queueWaitMs = +(operationStartedAt - enqueuedAt).toFixed(2);
|
|
1148
1176
|
this.logInfo('replaceProductsSnapshotInIndexDB 开始', {
|
|
1149
1177
|
source,
|
|
1150
|
-
productCount: products.length
|
|
1178
|
+
productCount: products.length,
|
|
1179
|
+
queueWaitMs
|
|
1151
1180
|
});
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1181
|
+
const clearStartedAt = performance.now();
|
|
1182
|
+
try {
|
|
1183
|
+
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
1184
|
+
} finally {
|
|
1185
|
+
clearDurationMs = +(performance.now() - clearStartedAt).toFixed(2);
|
|
1186
|
+
}
|
|
1154
1187
|
if (products.length > 0) {
|
|
1155
|
-
|
|
1188
|
+
const writeStartedAt = performance.now();
|
|
1189
|
+
try {
|
|
1190
|
+
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
|
|
1191
|
+
} finally {
|
|
1192
|
+
writeDurationMs = +(performance.now() - writeStartedAt).toFixed(2);
|
|
1193
|
+
}
|
|
1156
1194
|
}
|
|
1157
|
-
|
|
1195
|
+
const executeDurationMs = +(performance.now() - operationStartedAt).toFixed(2);
|
|
1196
|
+
(0, _product.perfMark)('replaceProductsSnapshotInIndexDB', executeDurationMs, {
|
|
1158
1197
|
count: products.length
|
|
1159
1198
|
});
|
|
1160
1199
|
this.logInfo('replaceProductsSnapshotInIndexDB 完成', {
|
|
1161
1200
|
source,
|
|
1162
|
-
productCount: products.length
|
|
1201
|
+
productCount: products.length,
|
|
1202
|
+
queueWaitMs,
|
|
1203
|
+
clearDurationMs,
|
|
1204
|
+
writeDurationMs,
|
|
1205
|
+
executeDurationMs,
|
|
1206
|
+
totalDurationMs: +(performance.now() - enqueuedAt).toFixed(2)
|
|
1163
1207
|
});
|
|
1164
1208
|
});
|
|
1165
1209
|
} catch (error) {
|
|
@@ -1167,6 +1211,10 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1167
1211
|
this.logError('全量保存商品到 IndexDB 失败', {
|
|
1168
1212
|
source,
|
|
1169
1213
|
productCount: products.length,
|
|
1214
|
+
queueWaitMs,
|
|
1215
|
+
clearDurationMs,
|
|
1216
|
+
writeDurationMs,
|
|
1217
|
+
totalDurationMs: +(performance.now() - enqueuedAt).toFixed(2),
|
|
1170
1218
|
error: errorMessage
|
|
1171
1219
|
});
|
|
1172
1220
|
}
|
|
@@ -1261,6 +1309,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1261
1309
|
source: 'IndexDB'
|
|
1262
1310
|
});
|
|
1263
1311
|
this.logInfo('预加载完成(从 IndexDB)', {
|
|
1312
|
+
trigger: 'preload',
|
|
1264
1313
|
productCount: cachedData.length,
|
|
1265
1314
|
duration: `${Math.round(performance.now() - tTotal)}ms`,
|
|
1266
1315
|
source: 'IndexDB'
|
|
@@ -1277,7 +1326,9 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1277
1326
|
});
|
|
1278
1327
|
}
|
|
1279
1328
|
const tServer = performance.now();
|
|
1280
|
-
const products = await this.loadProductsByServer(
|
|
1329
|
+
const products = await this.loadProductsByServer({
|
|
1330
|
+
trigger: 'preload'
|
|
1331
|
+
});
|
|
1281
1332
|
(0, _product.perfMark)('preload.loadFromServer', performance.now() - tServer, {
|
|
1282
1333
|
count: products?.length ?? 0
|
|
1283
1334
|
});
|
|
@@ -1295,6 +1346,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1295
1346
|
source: 'Server'
|
|
1296
1347
|
});
|
|
1297
1348
|
this.logInfo('预加载完成(从服务器)', {
|
|
1349
|
+
trigger: 'preload',
|
|
1298
1350
|
productCount: products.length,
|
|
1299
1351
|
duration: `${Math.round(performance.now() - tTotal)}ms`,
|
|
1300
1352
|
source: 'Server'
|
|
@@ -1708,17 +1760,33 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1708
1760
|
* 拿到完整数据后一次性替换 store,清除价格缓存,触发 onProductsSyncCompleted
|
|
1709
1761
|
* @returns 刷新后的商品列表
|
|
1710
1762
|
*/
|
|
1711
|
-
async silentRefresh() {
|
|
1763
|
+
async silentRefresh(options = {}) {
|
|
1712
1764
|
const t0 = performance.now();
|
|
1713
1765
|
const now = Date.now();
|
|
1714
|
-
const
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1766
|
+
const trigger = options.trigger || 'background';
|
|
1767
|
+
const elapsedSincePreloadMs = now - this.lastPreloadCompletedAtMs;
|
|
1768
|
+
const elapsedSinceFullFetchMs = now - this.lastServerFullFetchAtMs;
|
|
1769
|
+
if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreloadMs < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetchMs < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
|
|
1770
|
+
this.logInfo('silentRefresh 跳过全量 SSE', {
|
|
1771
|
+
trigger,
|
|
1772
|
+
reason: 'within_min_interval',
|
|
1773
|
+
minIntervalMs: PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS,
|
|
1774
|
+
elapsedSincePreloadMs,
|
|
1775
|
+
elapsedSinceFullFetchMs,
|
|
1776
|
+
productCount: this.store.list.length
|
|
1777
|
+
});
|
|
1717
1778
|
return this.store.list;
|
|
1718
1779
|
}
|
|
1719
|
-
this.logInfo('silentRefresh 开始'
|
|
1780
|
+
this.logInfo('silentRefresh 开始', {
|
|
1781
|
+
trigger,
|
|
1782
|
+
elapsedSincePreloadMs,
|
|
1783
|
+
elapsedSinceFullFetchMs,
|
|
1784
|
+
productCount: this.store.list.length
|
|
1785
|
+
});
|
|
1720
1786
|
try {
|
|
1721
|
-
const products = await this.loadProductsByServer(
|
|
1787
|
+
const products = await this.loadProductsByServer({
|
|
1788
|
+
trigger
|
|
1789
|
+
});
|
|
1722
1790
|
if (products && products.length > 0) {
|
|
1723
1791
|
this.store.list = products;
|
|
1724
1792
|
this.syncProductsMap();
|
|
@@ -1726,6 +1794,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1726
1794
|
this.core.effects.emit(_types.ProductsHooks.onProductsChanged, this.store.list);
|
|
1727
1795
|
await this.core.effects.emit(_types.ProductsHooks.onProductsSyncCompleted, null);
|
|
1728
1796
|
this.logInfo('silentRefresh 完成', {
|
|
1797
|
+
trigger,
|
|
1729
1798
|
productCount: products.length,
|
|
1730
1799
|
duration: `${Math.round(performance.now() - t0)}ms`
|
|
1731
1800
|
});
|
|
@@ -1739,6 +1808,7 @@ class ProductsModule extends _BaseModule.BaseModule {
|
|
|
1739
1808
|
} catch (error) {
|
|
1740
1809
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1741
1810
|
this.logError('silentRefresh 失败', {
|
|
1811
|
+
trigger,
|
|
1742
1812
|
duration: `${Math.round(performance.now() - t0)}ms`,
|
|
1743
1813
|
error: errorMessage
|
|
1744
1814
|
});
|
|
@@ -95,13 +95,23 @@ class ResourceModule extends _BaseModule.BaseModule {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
async preload() {
|
|
98
|
-
await this.setupResourceSync();
|
|
99
98
|
const cachedResources = await this.loadResourcesFromSQLite();
|
|
100
99
|
if (cachedResources.length > 0) {
|
|
101
100
|
this.store.list = (0, _lodashEs.cloneDeep)(cachedResources).map(item => this.normalizeResource(item));
|
|
102
101
|
this.syncResourcesMap();
|
|
103
102
|
await this.safeEmit(_types.ResourceHooks.onResourcesChanged, this.store.list);
|
|
104
|
-
|
|
103
|
+
|
|
104
|
+
// 二次启动优先使用 SQLite。pubsub 注册及远端校准继续在后台执行,
|
|
105
|
+
// setupResourceSync 完成后会合并最新数据并回写 SQLite。
|
|
106
|
+
void this.setupResourceSync().catch(error => {
|
|
107
|
+
this.logError('后台初始化资源同步失败', error);
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 首次启动没有本地数据时仍等待远端结果,保证进入业务页面前资源可用。
|
|
113
|
+
await this.setupResourceSync();
|
|
114
|
+
if (this.store.list.length === 0) {
|
|
105
115
|
const resources = await this.loadResourcesByServer();
|
|
106
116
|
if (resources.length > 0) {
|
|
107
117
|
this.store.list = (0, _lodashEs.cloneDeep)(resources).map(item => this.normalizeResource(item));
|
|
@@ -11,7 +11,7 @@ export declare function perfMark(label: string, durationMs: number, meta?: Recor
|
|
|
11
11
|
export declare function applyPriceDataToProducts(products: ProductData[], priceData: LoadProductsPriceData[]): ProductData[];
|
|
12
12
|
export declare const getIsSessionProduct: (product: ProductData) => boolean;
|
|
13
13
|
/**
|
|
14
|
-
* 根据 locale
|
|
14
|
+
* 根据 locale 格式化商品、分类、规格及套餐文案,缺少对应语言时回退 original。
|
|
15
15
|
*/
|
|
16
16
|
export declare function applyI18nToProducts(products: ProductData[], locale?: string): ProductData[];
|
|
17
17
|
/**
|
|
@@ -194,9 +194,139 @@ const I18N_FIELD_MAP = {
|
|
|
194
194
|
description_i18n: 'description',
|
|
195
195
|
cover_i18n: 'cover'
|
|
196
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* 按当前语言读取 i18n 文案;当前语言为空时回退到商家原始文案。
|
|
199
|
+
*/
|
|
200
|
+
function resolveI18nValue(i18nDict, locale) {
|
|
201
|
+
if (!i18nDict) return undefined;
|
|
202
|
+
return i18nDict[locale] || i18nDict.original || undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 递归格式化商品分类名称,同时保留未变化节点的引用。
|
|
207
|
+
*/
|
|
208
|
+
function applyI18nToCategory(category, locale) {
|
|
209
|
+
if (!category || typeof category !== 'object') return category;
|
|
210
|
+
const localizedName = resolveI18nValue(category.name_i18n, locale);
|
|
211
|
+
const localizedParent = applyI18nToCategory(category.parent_category, locale);
|
|
212
|
+
const nameChanged = localizedName !== undefined && localizedName !== category.name;
|
|
213
|
+
const parentChanged = localizedParent !== category.parent_category;
|
|
214
|
+
if (!nameChanged && !parentChanged) return category;
|
|
215
|
+
return {
|
|
216
|
+
...category,
|
|
217
|
+
...(nameChanged ? {
|
|
218
|
+
name: localizedName
|
|
219
|
+
} : {}),
|
|
220
|
+
...(parentChanged ? {
|
|
221
|
+
parent_category: localizedParent
|
|
222
|
+
} : {})
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function applyI18nToCategories(categories, locale) {
|
|
226
|
+
if (!Array.isArray(categories)) return categories;
|
|
227
|
+
let changed = false;
|
|
228
|
+
const localizedCategories = categories.map(category => {
|
|
229
|
+
const localizedCategory = applyI18nToCategory(category, locale);
|
|
230
|
+
if (localizedCategory !== category) changed = true;
|
|
231
|
+
return localizedCategory;
|
|
232
|
+
});
|
|
233
|
+
return changed ? localizedCategories : categories;
|
|
234
|
+
}
|
|
235
|
+
function applyI18nToNamedItems(items, locale) {
|
|
236
|
+
if (!Array.isArray(items)) return items;
|
|
237
|
+
let changed = false;
|
|
238
|
+
const localizedItems = items.map(item => {
|
|
239
|
+
if (!item || typeof item !== 'object') return item;
|
|
240
|
+
const localizedName = resolveI18nValue(item.name_i18n, locale);
|
|
241
|
+
if (localizedName === undefined || localizedName === item.name) return item;
|
|
242
|
+
changed = true;
|
|
243
|
+
return {
|
|
244
|
+
...item,
|
|
245
|
+
name: localizedName
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
return changed ? localizedItems : items;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 格式化选项组、规格组及其子项名称,同时保留未变化节点的引用。
|
|
253
|
+
*/
|
|
254
|
+
function applyI18nToNamedGroups(groups, itemKey, locale) {
|
|
255
|
+
if (!Array.isArray(groups)) return groups;
|
|
256
|
+
let changed = false;
|
|
257
|
+
const localizedGroups = groups.map(group => {
|
|
258
|
+
if (!group || typeof group !== 'object') return group;
|
|
259
|
+
const localizedName = resolveI18nValue(group.name_i18n, locale);
|
|
260
|
+
const localizedItems = applyI18nToNamedItems(group[itemKey], locale);
|
|
261
|
+
const nameChanged = localizedName !== undefined && localizedName !== group.name;
|
|
262
|
+
const itemsChanged = localizedItems !== group[itemKey];
|
|
263
|
+
if (!nameChanged && !itemsChanged) return group;
|
|
264
|
+
changed = true;
|
|
265
|
+
return {
|
|
266
|
+
...group,
|
|
267
|
+
...(nameChanged ? {
|
|
268
|
+
name: localizedName
|
|
269
|
+
} : {}),
|
|
270
|
+
...(itemsChanged ? {
|
|
271
|
+
[itemKey]: localizedItems
|
|
272
|
+
} : {})
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
return changed ? localizedGroups : groups;
|
|
276
|
+
}
|
|
277
|
+
function applyI18nToBundleItems(items, locale) {
|
|
278
|
+
if (!Array.isArray(items)) return items;
|
|
279
|
+
let changed = false;
|
|
280
|
+
const localizedItems = items.map(item => {
|
|
281
|
+
if (!item || typeof item !== 'object') return item;
|
|
282
|
+
const localizedTitle = resolveI18nValue(item.trans_title, locale);
|
|
283
|
+
const localizedOptionGroups = applyI18nToNamedGroups(item.option_group, 'option_item', locale);
|
|
284
|
+
const titleChanged = localizedTitle !== undefined && localizedTitle !== item.title;
|
|
285
|
+
const optionGroupsChanged = localizedOptionGroups !== item.option_group;
|
|
286
|
+
if (!titleChanged && !optionGroupsChanged) return item;
|
|
287
|
+
changed = true;
|
|
288
|
+
return {
|
|
289
|
+
...item,
|
|
290
|
+
...(titleChanged ? {
|
|
291
|
+
title: localizedTitle
|
|
292
|
+
} : {}),
|
|
293
|
+
...(optionGroupsChanged ? {
|
|
294
|
+
option_group: localizedOptionGroups
|
|
295
|
+
} : {})
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
return changed ? localizedItems : items;
|
|
299
|
+
}
|
|
197
300
|
|
|
198
301
|
/**
|
|
199
|
-
*
|
|
302
|
+
* 格式化套餐组名称、套餐子商品标题及子商品选项,同时保留未变化节点的引用。
|
|
303
|
+
*/
|
|
304
|
+
function applyI18nToBundleGroups(groups, locale) {
|
|
305
|
+
if (!Array.isArray(groups)) return groups;
|
|
306
|
+
let changed = false;
|
|
307
|
+
const localizedGroups = groups.map(group => {
|
|
308
|
+
if (!group || typeof group !== 'object') return group;
|
|
309
|
+
const localizedName = resolveI18nValue(group.name_i18n, locale);
|
|
310
|
+
const localizedItems = applyI18nToBundleItems(group.bundle_item, locale);
|
|
311
|
+
const nameChanged = localizedName !== undefined && localizedName !== group.name;
|
|
312
|
+
const itemsChanged = localizedItems !== group.bundle_item;
|
|
313
|
+
if (!nameChanged && !itemsChanged) return group;
|
|
314
|
+
changed = true;
|
|
315
|
+
return {
|
|
316
|
+
...group,
|
|
317
|
+
...(nameChanged ? {
|
|
318
|
+
name: localizedName
|
|
319
|
+
} : {}),
|
|
320
|
+
...(itemsChanged ? {
|
|
321
|
+
bundle_item: localizedItems
|
|
322
|
+
} : {})
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
return changed ? localizedGroups : groups;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 根据 locale 格式化商品、分类、规格及套餐文案,缺少对应语言时回退 original。
|
|
200
330
|
*/
|
|
201
331
|
function applyI18nToProducts(products, locale) {
|
|
202
332
|
if (!locale) return products;
|
|
@@ -205,14 +335,44 @@ function applyI18nToProducts(products, locale) {
|
|
|
205
335
|
for (const [i18nKey, originalKey] of Object.entries(I18N_FIELD_MAP)) {
|
|
206
336
|
const i18nDict = product[i18nKey];
|
|
207
337
|
if (!i18nDict) continue;
|
|
208
|
-
const localizedValue = i18nDict
|
|
209
|
-
if (localizedValue) {
|
|
338
|
+
const localizedValue = resolveI18nValue(i18nDict, locale);
|
|
339
|
+
if (localizedValue !== undefined && localizedValue !== product[originalKey]) {
|
|
210
340
|
if (!patched) patched = {
|
|
211
341
|
...product
|
|
212
342
|
};
|
|
213
343
|
patched[originalKey] = localizedValue;
|
|
214
344
|
}
|
|
215
345
|
}
|
|
346
|
+
for (const categoryKey of ['category', 'categories']) {
|
|
347
|
+
const categories = product[categoryKey];
|
|
348
|
+
const localizedCategories = applyI18nToCategories(categories, locale);
|
|
349
|
+
if (localizedCategories !== categories) {
|
|
350
|
+
if (!patched) patched = {
|
|
351
|
+
...product
|
|
352
|
+
};
|
|
353
|
+
patched[categoryKey] = localizedCategories;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for (const [groupKey, itemKey] of [['option_group', 'option_item'], ['variant_group', 'variant_item']]) {
|
|
357
|
+
const groups = product[groupKey];
|
|
358
|
+
const localizedGroups = applyI18nToNamedGroups(groups, itemKey, locale);
|
|
359
|
+
if (localizedGroups !== groups) {
|
|
360
|
+
if (!patched) patched = {
|
|
361
|
+
...product
|
|
362
|
+
};
|
|
363
|
+
patched[groupKey] = localizedGroups;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const bundleGroupKey of ['bundle_group', 'bundle_groups']) {
|
|
367
|
+
const groups = product[bundleGroupKey];
|
|
368
|
+
const localizedGroups = applyI18nToBundleGroups(groups, locale);
|
|
369
|
+
if (localizedGroups !== groups) {
|
|
370
|
+
if (!patched) patched = {
|
|
371
|
+
...product
|
|
372
|
+
};
|
|
373
|
+
patched[bundleGroupKey] = localizedGroups;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
216
376
|
return patched ?? product;
|
|
217
377
|
});
|
|
218
378
|
}
|
|
@@ -35,6 +35,8 @@ export interface SmallTicketProduct {
|
|
|
35
35
|
original_price: string;
|
|
36
36
|
total_original_price: string;
|
|
37
37
|
extension_list: any[];
|
|
38
|
+
product_uid?: string | number;
|
|
39
|
+
booking_uid?: string | number;
|
|
38
40
|
options?: Array<{
|
|
39
41
|
name: string;
|
|
40
42
|
num?: number;
|
|
@@ -44,6 +46,11 @@ export interface SmallTicketProduct {
|
|
|
44
46
|
variant?: string;
|
|
45
47
|
combinations?: any[];
|
|
46
48
|
}
|
|
49
|
+
export interface SmallTicketBookingSnapshot extends Record<string, any> {
|
|
50
|
+
product_uid?: string | number;
|
|
51
|
+
booking_uid?: string | number;
|
|
52
|
+
}
|
|
53
|
+
export type SmallTicketAppointment = SmallTicketItem[] | SmallTicketBookingSnapshot[];
|
|
47
54
|
export interface SmallTicketLocaleData {
|
|
48
55
|
base_data: Record<string, any>;
|
|
49
56
|
products: SmallTicketProduct[];
|
|
@@ -52,7 +59,7 @@ export interface SmallTicketLocaleData {
|
|
|
52
59
|
countFee: Record<string, any>;
|
|
53
60
|
};
|
|
54
61
|
customer: SmallTicketItem[];
|
|
55
|
-
appointment:
|
|
62
|
+
appointment: SmallTicketAppointment;
|
|
56
63
|
delivery?: SmallTicketItem[];
|
|
57
64
|
resources: Array<{
|
|
58
65
|
id: string | number;
|
|
@@ -61,6 +68,7 @@ export interface SmallTicketLocaleData {
|
|
|
61
68
|
form_title: string;
|
|
62
69
|
}>;
|
|
63
70
|
payment_data: SmallTicketItem[][];
|
|
71
|
+
wallets: SmallTicketItem[][];
|
|
64
72
|
tickets: any[];
|
|
65
73
|
source_shop: any[];
|
|
66
74
|
refund_data: any[];
|