@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.
Files changed (174) hide show
  1. package/dist/core/index.d.ts +7 -0
  2. package/dist/core/index.js +108 -65
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  7. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  8. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  9. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  10. package/dist/modules/BookingContext/index.d.ts +3 -0
  11. package/dist/modules/BookingContext/index.js +42 -13
  12. package/dist/modules/BookingContext/types.d.ts +4 -4
  13. package/dist/modules/BookingContext/types.js +3 -3
  14. package/dist/modules/OpenData/types.d.ts +1 -0
  15. package/dist/modules/Order/index.d.ts +18 -3
  16. package/dist/modules/Order/index.js +885 -485
  17. package/dist/modules/Order/salesNotes.d.ts +31 -0
  18. package/dist/modules/Order/salesNotes.js +492 -0
  19. package/dist/modules/Order/types.d.ts +93 -5
  20. package/dist/modules/Order/types.js +5 -0
  21. package/dist/modules/Order/utils.d.ts +12 -0
  22. package/dist/modules/Order/utils.js +96 -31
  23. package/dist/modules/Product/types.d.ts +2 -0
  24. package/dist/modules/ProductList/clientDataVariants.d.ts +50 -0
  25. package/dist/modules/ProductList/clientDataVariants.js +216 -0
  26. package/dist/modules/ProductList/index.d.ts +18 -3
  27. package/dist/modules/ProductList/index.js +220 -38
  28. package/dist/modules/ProductList/types.d.ts +10 -0
  29. package/dist/modules/Quotation/index.d.ts +22 -0
  30. package/dist/modules/Quotation/index.js +293 -28
  31. package/dist/modules/ResourcePlanner/index.d.ts +0 -22
  32. package/dist/modules/ResourcePlanner/index.js +1 -180
  33. package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
  34. package/dist/modules/ResourcePlanner/localClock.js +183 -0
  35. package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
  36. package/dist/modules/ResourcePlanner/planner.js +2111 -958
  37. package/dist/modules/ResourcePlanner/types.d.ts +359 -187
  38. package/dist/modules/ResourcePlanner/types.js +33 -1
  39. package/dist/plugins/request.d.ts +1 -0
  40. package/dist/server/index.d.ts +17 -2
  41. package/dist/server/index.js +1134 -932
  42. package/dist/server/modules/floor-plan/index.d.ts +4 -0
  43. package/dist/server/modules/floor-plan/index.js +240 -98
  44. package/dist/server/modules/menu/index.js +48 -23
  45. package/dist/server/modules/order/index.d.ts +17 -7
  46. package/dist/server/modules/order/index.js +419 -207
  47. package/dist/server/modules/products/index.d.ts +10 -4
  48. package/dist/server/modules/products/index.js +177 -84
  49. package/dist/server/modules/resource/index.js +21 -13
  50. package/dist/server/utils/product.d.ts +1 -1
  51. package/dist/server/utils/product.js +186 -22
  52. package/dist/server/utils/small-ticket.d.ts +9 -1
  53. package/dist/server/utils/small-ticket.js +131 -84
  54. package/dist/solution/BaseSales/index.d.ts +26 -3
  55. package/dist/solution/BaseSales/index.js +1384 -905
  56. package/dist/solution/BaseSales/types.d.ts +2 -0
  57. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  58. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +10 -3
  59. package/dist/solution/BaseSales/utils.js +6 -3
  60. package/dist/solution/BookingByStep/index.d.ts +4 -4
  61. package/dist/solution/BookingTicket/index.d.ts +1 -1
  62. package/dist/solution/BookingTicket/index.js +4 -2
  63. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  64. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -3
  65. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  66. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  67. package/dist/solution/RegisterAndLogin/config.js +95 -40
  68. package/dist/solution/RegisterAndLogin/index.js +4 -1
  69. package/dist/solution/ScanOrder/index.d.ts +1 -0
  70. package/dist/solution/ScanOrder/index.js +31 -27
  71. package/dist/solution/ScanOrder/types.d.ts +2 -0
  72. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  73. package/dist/solution/ScanOrder/utils.js +8 -3
  74. package/dist/solution/UnifiedBookingSales/index.d.ts +128 -74
  75. package/dist/solution/UnifiedBookingSales/index.js +3877 -963
  76. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  77. package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
  78. package/dist/solution/UnifiedBookingSales/types.d.ts +161 -77
  79. package/dist/solution/UnifiedBookingSales/types.js +4 -9
  80. package/dist/solution/VenueBooking/index.d.ts +3 -0
  81. package/dist/solution/VenueBooking/index.js +549 -497
  82. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  83. package/dist/solution/VenueBooking/utils/dateSummary.js +3 -1
  84. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  85. package/dist/solution/VenueBooking/utils/slotMerge.js +3 -1
  86. package/dist/utils/platform.d.ts +8 -0
  87. package/dist/utils/platform.js +15 -0
  88. package/lib/core/index.d.ts +7 -0
  89. package/lib/core/index.js +20 -2
  90. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  91. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  92. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  93. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  94. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  95. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  96. package/lib/modules/BookingContext/index.d.ts +3 -0
  97. package/lib/modules/BookingContext/index.js +35 -8
  98. package/lib/modules/BookingContext/types.d.ts +4 -4
  99. package/lib/modules/BookingContext/types.js +3 -3
  100. package/lib/modules/OpenData/types.d.ts +1 -0
  101. package/lib/modules/Order/index.d.ts +18 -3
  102. package/lib/modules/Order/index.js +325 -28
  103. package/lib/modules/Order/salesNotes.d.ts +31 -0
  104. package/lib/modules/Order/salesNotes.js +382 -0
  105. package/lib/modules/Order/types.d.ts +93 -5
  106. package/lib/modules/Order/types.js +4 -0
  107. package/lib/modules/Order/utils.d.ts +12 -0
  108. package/lib/modules/Order/utils.js +85 -16
  109. package/lib/modules/Product/types.d.ts +2 -0
  110. package/lib/modules/ProductList/clientDataVariants.d.ts +50 -0
  111. package/lib/modules/ProductList/clientDataVariants.js +204 -0
  112. package/lib/modules/ProductList/index.d.ts +18 -3
  113. package/lib/modules/ProductList/index.js +137 -8
  114. package/lib/modules/ProductList/types.d.ts +10 -0
  115. package/lib/modules/Quotation/index.d.ts +22 -0
  116. package/lib/modules/Quotation/index.js +158 -11
  117. package/lib/modules/ResourcePlanner/index.d.ts +0 -22
  118. package/lib/modules/ResourcePlanner/index.js +9 -129
  119. package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
  120. package/lib/modules/ResourcePlanner/localClock.js +205 -0
  121. package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
  122. package/lib/modules/ResourcePlanner/planner.js +1807 -783
  123. package/lib/modules/ResourcePlanner/types.d.ts +359 -187
  124. package/lib/modules/ResourcePlanner/types.js +13 -1
  125. package/lib/plugins/request.d.ts +1 -0
  126. package/lib/server/index.d.ts +17 -2
  127. package/lib/server/index.js +149 -21
  128. package/lib/server/modules/floor-plan/index.d.ts +4 -0
  129. package/lib/server/modules/floor-plan/index.js +60 -7
  130. package/lib/server/modules/menu/index.js +26 -4
  131. package/lib/server/modules/order/index.d.ts +17 -7
  132. package/lib/server/modules/order/index.js +194 -57
  133. package/lib/server/modules/products/index.d.ts +10 -4
  134. package/lib/server/modules/products/index.js +93 -23
  135. package/lib/server/modules/resource/index.js +12 -2
  136. package/lib/server/utils/product.d.ts +1 -1
  137. package/lib/server/utils/product.js +163 -3
  138. package/lib/server/utils/small-ticket.d.ts +9 -1
  139. package/lib/server/utils/small-ticket.js +123 -79
  140. package/lib/solution/BaseSales/index.d.ts +26 -3
  141. package/lib/solution/BaseSales/index.js +396 -70
  142. package/lib/solution/BaseSales/types.d.ts +2 -0
  143. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +3 -0
  144. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -2
  145. package/lib/solution/BaseSales/utils.js +3 -0
  146. package/lib/solution/BookingByStep/index.d.ts +4 -4
  147. package/lib/solution/BookingTicket/index.d.ts +1 -1
  148. package/lib/solution/BookingTicket/index.js +2 -1
  149. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
  150. package/lib/solution/BookingTicket/utils/addProductDecision.js +5 -2
  151. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  152. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  153. package/lib/solution/RegisterAndLogin/config.js +49 -8
  154. package/lib/solution/RegisterAndLogin/index.js +4 -1
  155. package/lib/solution/ScanOrder/index.d.ts +1 -0
  156. package/lib/solution/ScanOrder/index.js +5 -1
  157. package/lib/solution/ScanOrder/types.d.ts +2 -0
  158. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  159. package/lib/solution/ScanOrder/utils.js +4 -0
  160. package/lib/solution/UnifiedBookingSales/index.d.ts +128 -74
  161. package/lib/solution/UnifiedBookingSales/index.js +2592 -424
  162. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
  163. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +316 -0
  164. package/lib/solution/UnifiedBookingSales/types.d.ts +161 -77
  165. package/lib/solution/UnifiedBookingSales/types.js +4 -6
  166. package/lib/solution/VenueBooking/index.d.ts +3 -0
  167. package/lib/solution/VenueBooking/index.js +33 -9
  168. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  169. package/lib/solution/VenueBooking/utils/dateSummary.js +3 -1
  170. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +2 -0
  171. package/lib/solution/VenueBooking/utils/slotMerge.js +3 -1
  172. package/lib/utils/platform.d.ts +8 -0
  173. package/lib/utils/platform.js +22 -0
  174. package/package.json +1 -1
@@ -30,7 +30,7 @@ class ProductsModule extends _BaseModule.BaseModule {
30
30
  logger; // LoggerManager 实例
31
31
  otherParams = {};
32
32
 
33
- // 商品价格缓存:Map<日期, 应用了价格的商品列表>
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
- * 使用日期作为缓存 key,同一天的商品价格会被缓存
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 ?? ''}:strategy:${strategyScope}${productScope}`;
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
- this.logInfo('开始通过 DataSource SSE 加载商品列表');
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
- (0, _product.perfMark)('loadProductsFromIndexDB', performance.now() - t0, {
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 t0 = performance.now();
1153
- await this.dbManager.clear(INDEXDB_STORE_NAME);
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
- await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
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
- (0, _product.perfMark)('replaceProductsSnapshotInIndexDB', performance.now() - t0, {
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 elapsedSincePreload = now - this.lastPreloadCompletedAtMs;
1715
- const elapsedSinceFullFetch = now - this.lastServerFullFetchAtMs;
1716
- if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreload < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
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
- } else {
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 将商品的 i18n 字段覆盖到对应原始字段
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
- * 根据 locale 将商品的 i18n 字段覆盖到对应原始字段
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[locale];
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: SmallTicketItem[];
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[];