@pisell/pisellos 2.2.89 → 2.2.91

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.
@@ -109,6 +109,7 @@ export interface ProductFormatterContext {
109
109
  schedule_date: string;
110
110
  priceData?: LoadProductsPriceData[];
111
111
  scheduleModule?: any;
112
+ locale?: string;
112
113
  }
113
114
  /**
114
115
  * 商品格式化器类型
@@ -10,6 +10,10 @@ export declare function perfMark(label: string, durationMs: number, meta?: Recor
10
10
  */
11
11
  export declare function applyPriceDataToProducts(products: ProductData[], priceData: LoadProductsPriceData[]): ProductData[];
12
12
  export declare const getIsSessionProduct: (product: ProductData) => boolean;
13
+ /**
14
+ * 根据 locale 将商品的 i18n 字段覆盖到对应原始字段
15
+ */
16
+ export declare function applyI18nToProducts(products: ProductData[], locale?: string): ProductData[];
13
17
  /**
14
18
  * 将详情值数据应用到商品列表
15
19
  * @param products 商品列表
@@ -1,7 +1,11 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
2
  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
3
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
4
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
5
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
6
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
7
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
8
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
5
9
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
6
10
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
7
11
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -186,6 +190,36 @@ var getIsOpenDetailModal = function getIsOpenDetailModal(product, context) {
186
190
  scheduleTimeSlots: scheduleTimeSlots
187
191
  };
188
192
  };
193
+ var I18N_FIELD_MAP = {
194
+ title_i18n: 'title',
195
+ subtitle_i18n: 'subtitle',
196
+ description_i18n: 'description',
197
+ cover_i18n: 'cover'
198
+ };
199
+
200
+ /**
201
+ * 根据 locale 将商品的 i18n 字段覆盖到对应原始字段
202
+ */
203
+ export function applyI18nToProducts(products, locale) {
204
+ if (!locale) return products;
205
+ return products.map(function (product) {
206
+ var _patched;
207
+ var patched = null;
208
+ for (var _i = 0, _Object$entries = Object.entries(I18N_FIELD_MAP); _i < _Object$entries.length; _i++) {
209
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
210
+ i18nKey = _Object$entries$_i[0],
211
+ originalKey = _Object$entries$_i[1];
212
+ var i18nDict = product[i18nKey];
213
+ if (!i18nDict) continue;
214
+ var localizedValue = i18nDict[locale];
215
+ if (localizedValue) {
216
+ if (!patched) patched = _objectSpread({}, product);
217
+ patched[originalKey] = localizedValue;
218
+ }
219
+ }
220
+ return (_patched = patched) !== null && _patched !== void 0 ? _patched : product;
221
+ });
222
+ }
189
223
  var formatDataKey = function formatDataKey(data) {
190
224
  var _data$option, _data$bundle;
191
225
  var _data = _objectSpread({}, data);
@@ -52,6 +52,7 @@ export interface PisellCore {
52
52
  validateContext: (config: ModuleContextConfig) => boolean;
53
53
  serverOptions?: ServerOptions;
54
54
  server?: any;
55
+ setContext: (ctx: Partial<BusinessContext>) => void;
55
56
  }
56
57
  /**
57
58
  * 业务上下文接口
@@ -59,6 +60,7 @@ export interface PisellCore {
59
60
  export interface BusinessContext {
60
61
  userId?: string;
61
62
  companyId?: string;
63
+ locale?: string;
62
64
  [key: string]: any;
63
65
  }
64
66
  /**
@@ -29,6 +29,7 @@ declare class PisellOSCore implements PisellCore {
29
29
  getModuleExports<T = any>(name: string): T | null;
30
30
  hasModule(name: string): boolean;
31
31
  destroy(): Promise<void>;
32
+ setContext(ctx: Partial<BusinessContext>): void;
32
33
  /**
33
34
  * 验证上下文参数
34
35
  */
package/lib/core/index.js CHANGED
@@ -235,6 +235,10 @@ var PisellOSCore = class {
235
235
  this.plugins.clear();
236
236
  this.log("PisellOS 核心已销毁");
237
237
  }
238
+ setContext(ctx) {
239
+ this.context = { ...this.context, ...ctx };
240
+ this.log(`上下文已更新: ${JSON.stringify(Object.keys(ctx))}`);
241
+ }
238
242
  /**
239
243
  * 验证上下文参数
240
244
  */
@@ -154,11 +154,16 @@ declare class Server {
154
154
  /**
155
155
  * 商品查询的核心计算逻辑(编排 Products、Menu、Schedule 模块)
156
156
  * 供 handleProductQuery 首次返回及 pubsub 变更推送复用
157
+ * @param context 查询上下文
158
+ * @param options 可选参数
159
+ * @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
157
160
  */
158
161
  private computeProductQueryResult;
159
162
  /**
160
163
  * 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
161
164
  * 由 ProductsModule 的 onProductsSyncCompleted 事件触发
165
+ * @param options 可选参数
166
+ * @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
162
167
  */
163
168
  private recomputeAndNotifyProductQuery;
164
169
  /**
@@ -415,8 +415,10 @@ var Server = class {
415
415
  } else {
416
416
  this.logInfo("跳过自动预加载", { autoPreload });
417
417
  }
418
- this.core.effects.on(import_types.ProductsHooks.onProductsSyncCompleted, () => {
419
- this.recomputeAndNotifyProductQuery();
418
+ this.core.effects.on(import_types.ProductsHooks.onProductsSyncCompleted, (payload) => {
419
+ this.recomputeAndNotifyProductQuery({
420
+ changedIds: payload == null ? void 0 : payload.changedIds
421
+ });
420
422
  });
421
423
  const duration = Date.now() - startTime;
422
424
  this.logInfo("Server 初始化完成", {
@@ -654,14 +656,18 @@ var Server = class {
654
656
  /**
655
657
  * 商品查询的核心计算逻辑(编排 Products、Menu、Schedule 模块)
656
658
  * 供 handleProductQuery 首次返回及 pubsub 变更推送复用
659
+ * @param context 查询上下文
660
+ * @param options 可选参数
661
+ * @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
657
662
  */
658
- async computeProductQueryResult(context) {
663
+ async computeProductQueryResult(context, options) {
659
664
  const tTotal = performance.now();
660
665
  const { menu_list_ids, schedule_date, schedule_datetime } = context;
661
666
  this.logInfo("computeProductQueryResult 开始", {
662
667
  menuListIdsCount: (menu_list_ids == null ? void 0 : menu_list_ids.length) ?? 0,
663
668
  schedule_datetime,
664
- schedule_date
669
+ schedule_date,
670
+ changedIds: options == null ? void 0 : options.changedIds
665
671
  });
666
672
  if (!this.products) {
667
673
  this.logError("computeProductQueryResult: Products 模块未注册");
@@ -691,6 +697,8 @@ var Server = class {
691
697
  const tPrice = performance.now();
692
698
  const allProductsWithPrice = await this.products.getProductsWithPrice(schedule_date, {
693
699
  scheduleModule: this.getSchedule()
700
+ }, {
701
+ changedIds: options == null ? void 0 : options.changedIds
694
702
  });
695
703
  (0, import_product.perfMark)("computeQuery.getProductsWithPrice", performance.now() - tPrice, {
696
704
  count: allProductsWithPrice.length
@@ -736,16 +744,21 @@ var Server = class {
736
744
  /**
737
745
  * 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
738
746
  * 由 ProductsModule 的 onProductsSyncCompleted 事件触发
747
+ * @param options 可选参数
748
+ * @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
739
749
  */
740
- async recomputeAndNotifyProductQuery() {
750
+ async recomputeAndNotifyProductQuery(options) {
741
751
  if (this.productQuerySubscribers.size === 0)
742
752
  return;
743
753
  this.logInfo("recomputeAndNotifyProductQuery: 开始推送", {
744
- subscriberCount: this.productQuerySubscribers.size
754
+ subscriberCount: this.productQuerySubscribers.size,
755
+ changedIds: options == null ? void 0 : options.changedIds
745
756
  });
746
757
  for (const [subscriberId, subscriber] of this.productQuerySubscribers.entries()) {
747
758
  try {
748
- const result = await this.computeProductQueryResult(subscriber.context);
759
+ const result = await this.computeProductQueryResult(subscriber.context, {
760
+ changedIds: options == null ? void 0 : options.changedIds
761
+ });
749
762
  subscriber.callback(result);
750
763
  this.logInfo("recomputeAndNotifyProductQuery: 已推送", { subscriberId });
751
764
  } catch (error) {
@@ -53,14 +53,20 @@ export declare class ProductsModule extends BaseModule implements Module {
53
53
  * 缓存的是已经应用了价格的完整商品列表,避免重复转换
54
54
  * @param schedule_date 日期
55
55
  * @param extraContext 额外的上下文数据(可选,由 Server 层传入)
56
+ * @param options 可选参数
57
+ * @param options.changedIds 变更的商品 IDs,非空时仅对这些商品增量执行 prepare 并更新缓存
56
58
  * @returns 应用了价格的商品列表
57
59
  */
58
- getProductsWithPrice(schedule_date: string, extraContext?: Partial<ProductFormatterContext>): Promise<ProductData[]>;
60
+ getProductsWithPrice(schedule_date: string, extraContext?: Partial<ProductFormatterContext>, options?: {
61
+ changedIds?: number[];
62
+ }): Promise<ProductData[]>;
59
63
  /**
60
64
  * 准备带价格的商品数据(通过格式化器流程处理)
61
65
  * @param schedule_date 日期
62
66
  * @param extraContext 额外的上下文数据(可选)
63
- * @returns 完整处理后的商品列表
67
+ * @param options 可选参数
68
+ * @param options.productIds 指定商品 IDs,仅处理这些商品;不传则处理全量
69
+ * @returns 处理后的商品列表
64
70
  * @private
65
71
  */
66
72
  private prepareProductsWithPrice;
@@ -156,11 +162,6 @@ export declare class ProductsModule extends BaseModule implements Module {
156
162
  * 用于 pubsub 同步 create / update / batch_update 场景
157
163
  */
158
164
  refreshProducts(): Promise<ProductData[]>;
159
- /**
160
- * 指定商品的报价单价格变更时,清除价格缓存
161
- * 后续查询会走完整的 formatter 管道重建缓存
162
- */
163
- updateProductPriceByIds(ids: number[]): Promise<void>;
164
165
  /**
165
166
  * 清空缓存
166
167
  */
@@ -202,14 +203,18 @@ export declare class ProductsModule extends BaseModule implements Module {
202
203
  *
203
204
  * product 模块:
204
205
  * - operation === 'delete' → 本地删除
205
- * - change_types 包含 price → SSE 增量拉取
206
+ * - change_types 包含 price → 仅收集变更 IDs(不拉商品数据)
206
207
  * - 有 body → body 完整数据直接覆盖本地
208
+ * - 其他 → SSE 增量拉取
207
209
  *
208
- * product_collection / product_category / product_quotation
210
+ * product_collection / product_category:
209
211
  * - 按 relation_product_ids SSE 拉取受影响商品
210
212
  *
211
- * 处理完成后清除价格缓存并 emit onProductsSyncCompleted,
212
- * Server 层监听该事件后重新查询,走完整的 formatter 管道重建缓存
213
+ * product_quotation:
214
+ * - 报价单变更影响范围大,直接清除价格缓存走全量重建
215
+ *
216
+ * 处理完成后 emit onProductsSyncCompleted(携带 changedIds),
217
+ * Server 层监听该事件后对变更商品增量执行 prepareProductsWithPrice 并更新缓存
213
218
  */
214
219
  private processProductSyncMessages;
215
220
  /**
@@ -177,7 +177,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
177
177
  duration: `${duration}ms`,
178
178
  error: errorMessage
179
179
  });
180
- throw error;
180
+ return [];
181
181
  }
182
182
  }
183
183
  /**
@@ -186,13 +186,70 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
186
186
  * 缓存的是已经应用了价格的完整商品列表,避免重复转换
187
187
  * @param schedule_date 日期
188
188
  * @param extraContext 额外的上下文数据(可选,由 Server 层传入)
189
+ * @param options 可选参数
190
+ * @param options.changedIds 变更的商品 IDs,非空时仅对这些商品增量执行 prepare 并更新缓存
189
191
  * @returns 应用了价格的商品列表
190
192
  */
191
- async getProductsWithPrice(schedule_date, extraContext) {
193
+ async getProductsWithPrice(schedule_date, extraContext, options) {
192
194
  const t0 = performance.now();
193
195
  const cacheKey = schedule_date;
196
+ const changedIds = options == null ? void 0 : options.changedIds;
194
197
  if (this.productsPriceCache.has(cacheKey)) {
195
198
  const cachedProducts = this.productsPriceCache.get(cacheKey);
199
+ if (changedIds && changedIds.length > 0) {
200
+ this.logInfo("商品价格缓存命中,增量更新变更商品", {
201
+ cacheKey,
202
+ changedIds,
203
+ cachedProductCount: cachedProducts.length
204
+ });
205
+ try {
206
+ const updatedProducts = await this.prepareProductsWithPrice(
207
+ schedule_date,
208
+ extraContext,
209
+ { productIds: changedIds }
210
+ );
211
+ if (updatedProducts.length > 0) {
212
+ const updatedMap = new Map(updatedProducts.map((p) => [p.id, p]));
213
+ const mergedCache = [];
214
+ for (const p of cachedProducts) {
215
+ if (updatedMap.has(p.id)) {
216
+ mergedCache.push(updatedMap.get(p.id));
217
+ updatedMap.delete(p.id);
218
+ } else {
219
+ mergedCache.push(p);
220
+ }
221
+ }
222
+ for (const p of updatedMap.values()) {
223
+ mergedCache.push(p);
224
+ }
225
+ this.productsPriceCache.set(cacheKey, mergedCache);
226
+ this.logInfo("增量更新完成", {
227
+ cacheKey,
228
+ changedCount: updatedProducts.length,
229
+ totalCount: mergedCache.length
230
+ });
231
+ (0, import_product.perfMark)("getProductsWithPrice(incrementalUpdate)", performance.now() - t0, {
232
+ cacheKey,
233
+ changedCount: changedIds.length,
234
+ count: mergedCache.length
235
+ });
236
+ return mergedCache;
237
+ }
238
+ } catch (error) {
239
+ const errorMessage = error instanceof Error ? error.message : String(error);
240
+ this.logError("增量更新失败,返回现有缓存", {
241
+ cacheKey,
242
+ changedIds,
243
+ error: errorMessage
244
+ });
245
+ }
246
+ (0, import_product.perfMark)("getProductsWithPrice(incrementalUpdate)", performance.now() - t0, {
247
+ cacheKey,
248
+ changedCount: changedIds.length,
249
+ count: cachedProducts.length
250
+ });
251
+ return cachedProducts;
252
+ }
196
253
  (0, import_product.perfMark)("getProductsWithPrice(cacheHit)", performance.now() - t0, {
197
254
  cacheKey,
198
255
  count: cachedProducts.length
@@ -221,21 +278,38 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
221
278
  * 准备带价格的商品数据(通过格式化器流程处理)
222
279
  * @param schedule_date 日期
223
280
  * @param extraContext 额外的上下文数据(可选)
224
- * @returns 完整处理后的商品列表
281
+ * @param options 可选参数
282
+ * @param options.productIds 指定商品 IDs,仅处理这些商品;不传则处理全量
283
+ * @returns 处理后的商品列表
225
284
  * @private
226
285
  */
227
- async prepareProductsWithPrice(schedule_date, extraContext) {
286
+ async prepareProductsWithPrice(schedule_date, extraContext, options) {
287
+ var _a, _b;
228
288
  const tTotal = performance.now();
229
- this.logInfo("prepareProductsWithPrice 开始处理", { schedule_date });
289
+ const targetIds = options == null ? void 0 : options.productIds;
290
+ const isIncremental = targetIds && targetIds.length > 0;
291
+ this.logInfo("prepareProductsWithPrice 开始处理", {
292
+ schedule_date,
293
+ mode: isIncremental ? "incremental" : "full",
294
+ targetIdsCount: targetIds == null ? void 0 : targetIds.length
295
+ });
230
296
  try {
231
- const allProducts = this.getProductsRef();
232
- this.logInfo("获取到商品列表", { productCount: allProducts.length });
233
- const tIds = performance.now();
234
- const ids = new Array(allProducts.length);
235
- for (let i = 0; i < allProducts.length; i++) {
236
- ids[i] = allProducts[i].id;
297
+ let products;
298
+ let ids;
299
+ if (isIncremental) {
300
+ const idSet = new Set(targetIds);
301
+ products = this.getProductsRef().filter((p) => idSet.has(p.id));
302
+ ids = products.map((p) => p.id);
303
+ } else {
304
+ products = this.getProductsRef();
305
+ const tIds = performance.now();
306
+ ids = new Array(products.length);
307
+ for (let i = 0; i < products.length; i++) {
308
+ ids[i] = products[i].id;
309
+ }
310
+ (0, import_product.perfMark)("prepareProducts.extractIds", performance.now() - tIds, { count: ids.length });
237
311
  }
238
- (0, import_product.perfMark)("prepareProducts.extractIds", performance.now() - tIds, { count: ids.length });
312
+ this.logInfo("获取到商品列表", { productCount: products.length });
239
313
  const tPrice = performance.now();
240
314
  const priceData = await this.loadProductsPrice({
241
315
  ids,
@@ -246,26 +320,31 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
246
320
  const context = {
247
321
  schedule_date,
248
322
  priceData,
323
+ locale: (_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.locale,
249
324
  ...extraContext
250
325
  };
251
326
  const tFormat = performance.now();
252
- const processedProducts = await this.applyFormatters(allProducts, context);
327
+ const processedProducts = await this.applyFormatters(products, context);
253
328
  (0, import_product.perfMark)("prepareProducts.applyFormatters", performance.now() - tFormat, {
254
- count: allProducts.length,
329
+ count: products.length,
255
330
  formatterCount: this.formatters.length
256
331
  });
257
332
  this.logInfo("prepareProductsWithPrice 处理完成", {
258
- originalProductCount: allProducts.length,
333
+ mode: isIncremental ? "incremental" : "full",
334
+ originalProductCount: products.length,
259
335
  processedProductCount: processedProducts.length,
260
336
  formatterCount: this.formatters.length
261
337
  });
262
- await this.core.effects.emit(
263
- import_types.ProductsHooks.onProductsPriceApplied,
264
- processedProducts
265
- );
338
+ if (!isIncremental) {
339
+ await this.core.effects.emit(
340
+ import_types.ProductsHooks.onProductsPriceApplied,
341
+ processedProducts
342
+ );
343
+ }
266
344
  (0, import_product.perfMark)("prepareProductsWithPrice", performance.now() - tTotal, {
267
- productCount: allProducts.length,
268
- formatterCount: this.formatters.length
345
+ productCount: products.length,
346
+ formatterCount: this.formatters.length,
347
+ mode: isIncremental ? "incremental" : "full"
269
348
  });
270
349
  return processedProducts;
271
350
  } catch (err) {
@@ -340,10 +419,14 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
340
419
  console.log(`[ProductsModule] 💰 应用价格数据到 ${products.length} 个商品`);
341
420
  return (0, import_product.applyPriceDataToProducts)(products, context.priceData);
342
421
  };
422
+ const i18nFormatter = (products, context) => {
423
+ return (0, import_product.applyI18nToProducts)(products, context.locale);
424
+ };
343
425
  const detailValueFormatter = (products, context) => {
344
426
  return (0, import_product.applyDetailValueToProducts)(products, context);
345
427
  };
346
428
  this.formatters.unshift(priceFormatter);
429
+ this.formatters.push(i18nFormatter);
347
430
  this.formatters.push(detailValueFormatter);
348
431
  this.isPriceFormatterRegistered = true;
349
432
  console.log("[ProductsModule] ✅ 内置价格格式化器已注册(第 1 个)");
@@ -604,14 +687,6 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
604
687
  (0, import_product.perfMark)("refreshProducts", performance.now() - tTotal, { count: this.store.list.length });
605
688
  return this.store.list;
606
689
  }
607
- /**
608
- * 指定商品的报价单价格变更时,清除价格缓存
609
- * 后续查询会走完整的 formatter 管道重建缓存
610
- */
611
- async updateProductPriceByIds(ids) {
612
- this.logInfo("updateProductPriceByIds: 清除价格缓存", { ids });
613
- this.clearPriceCache();
614
- }
615
690
  /**
616
691
  * 清空缓存
617
692
  */
@@ -838,17 +913,21 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
838
913
  *
839
914
  * product 模块:
840
915
  * - operation === 'delete' → 本地删除
841
- * - change_types 包含 price → SSE 增量拉取
916
+ * - change_types 包含 price → 仅收集变更 IDs(不拉商品数据)
842
917
  * - 有 body → body 完整数据直接覆盖本地
918
+ * - 其他 → SSE 增量拉取
843
919
  *
844
- * product_collection / product_category / product_quotation
920
+ * product_collection / product_category:
845
921
  * - 按 relation_product_ids SSE 拉取受影响商品
846
922
  *
847
- * 处理完成后清除价格缓存并 emit onProductsSyncCompleted,
848
- * Server 层监听该事件后重新查询,走完整的 formatter 管道重建缓存
923
+ * product_quotation:
924
+ * - 报价单变更影响范围大,直接清除价格缓存走全量重建
925
+ *
926
+ * 处理完成后 emit onProductsSyncCompleted(携带 changedIds),
927
+ * Server 层监听该事件后对变更商品增量执行 prepareProductsWithPrice 并更新缓存
849
928
  */
850
929
  async processProductSyncMessages() {
851
- var _a, _b, _c, _d, _e;
930
+ var _a, _b, _c, _d, _e, _f;
852
931
  const messages = [...this.pendingSyncMessages];
853
932
  this.pendingSyncMessages = [];
854
933
  if (messages.length === 0)
@@ -857,6 +936,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
857
936
  const deleteIds = [];
858
937
  const bodyUpdates = /* @__PURE__ */ new Map();
859
938
  const sseRefreshIds = [];
939
+ const priceRefreshIds = [];
940
+ let shouldClearPriceCache = false;
860
941
  for (const msg of messages) {
861
942
  const channelKey = msg._channelKey || msg.module || "product";
862
943
  if (channelKey === "product") {
@@ -872,7 +953,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
872
953
  }
873
954
  if ((_c = msg.change_types) == null ? void 0 : _c.includes("price")) {
874
955
  const ids = msg.ids || (msg.id ? [msg.id] : []);
875
- sseRefreshIds.push(...ids);
956
+ priceRefreshIds.push(...ids);
876
957
  continue;
877
958
  }
878
959
  if (msg.body) {
@@ -886,14 +967,20 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
886
967
  } else if (msg.id) {
887
968
  sseRefreshIds.push(msg.id);
888
969
  }
889
- } else if (["product_collection", "product_category", "product_quotation"].includes(channelKey)) {
970
+ } else if (channelKey === "product_quotation") {
971
+ shouldClearPriceCache = true;
890
972
  if ((_e = msg.relation_product_ids) == null ? void 0 : _e.length) {
891
973
  sseRefreshIds.push(...msg.relation_product_ids);
892
974
  }
975
+ } else if (["product_collection", "product_category"].includes(channelKey)) {
976
+ if ((_f = msg.relation_product_ids) == null ? void 0 : _f.length) {
977
+ sseRefreshIds.push(...msg.relation_product_ids);
978
+ }
893
979
  }
894
980
  }
895
981
  const uniqueDeleteIds = [...new Set(deleteIds)];
896
982
  const uniqueSSEIds = [...new Set(sseRefreshIds)];
983
+ const uniquePriceIds = [...new Set(priceRefreshIds)];
897
984
  if (uniqueDeleteIds.length > 0) {
898
985
  await this.removeProductsByIds(uniqueDeleteIds);
899
986
  }
@@ -910,17 +997,30 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
910
997
  receivedCount: freshProducts.length
911
998
  });
912
999
  }
1000
+ const allChangedIds = [.../* @__PURE__ */ new Set([
1001
+ ...Array.from(bodyUpdates.keys()),
1002
+ ...uniqueSSEIds,
1003
+ ...uniquePriceIds
1004
+ ])];
913
1005
  this.logInfo("processProductSyncMessages: 处理完成", {
914
1006
  deleteCount: uniqueDeleteIds.length,
915
1007
  bodyUpdateCount: bodyUpdates.size,
916
- sseRefreshCount: uniqueSSEIds.length
1008
+ sseRefreshCount: uniqueSSEIds.length,
1009
+ priceRefreshCount: uniquePriceIds.length,
1010
+ allChangedIdsCount: allChangedIds.length,
1011
+ shouldClearPriceCache
917
1012
  });
918
- if (uniqueDeleteIds.length === 0 && bodyUpdates.size === 0 && uniqueSSEIds.length === 0) {
1013
+ const hasChanges = uniqueDeleteIds.length > 0 || allChangedIds.length > 0 || shouldClearPriceCache;
1014
+ if (!hasChanges) {
919
1015
  this.logInfo("processProductSyncMessages: 没有变更,不触发 onProductsSyncCompleted");
920
1016
  return;
921
1017
  }
922
- this.clearPriceCache();
923
- await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, null);
1018
+ if (shouldClearPriceCache) {
1019
+ this.clearPriceCache();
1020
+ }
1021
+ await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, {
1022
+ changedIds: allChangedIds
1023
+ });
924
1024
  }
925
1025
  /**
926
1026
  * 通过 SSE 按 ids 增量拉取商品数据
@@ -109,6 +109,7 @@ export interface ProductFormatterContext {
109
109
  schedule_date: string;
110
110
  priceData?: LoadProductsPriceData[];
111
111
  scheduleModule?: any;
112
+ locale?: string;
112
113
  }
113
114
  /**
114
115
  * 商品格式化器类型
@@ -10,6 +10,10 @@ export declare function perfMark(label: string, durationMs: number, meta?: Recor
10
10
  */
11
11
  export declare function applyPriceDataToProducts(products: ProductData[], priceData: LoadProductsPriceData[]): ProductData[];
12
12
  export declare const getIsSessionProduct: (product: ProductData) => boolean;
13
+ /**
14
+ * 根据 locale 将商品的 i18n 字段覆盖到对应原始字段
15
+ */
16
+ export declare function applyI18nToProducts(products: ProductData[], locale?: string): ProductData[];
13
17
  /**
14
18
  * 将详情值数据应用到商品列表
15
19
  * @param products 商品列表
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var product_exports = {};
21
21
  __export(product_exports, {
22
22
  applyDetailValueToProducts: () => applyDetailValueToProducts,
23
+ applyI18nToProducts: () => applyI18nToProducts,
23
24
  applyPriceDataToProducts: () => applyPriceDataToProducts,
24
25
  getIsSessionProduct: () => getIsSessionProduct,
25
26
  perfMark: () => perfMark
@@ -155,6 +156,31 @@ var getIsOpenDetailModal = (product, context) => {
155
156
  scheduleTimeSlots
156
157
  };
157
158
  };
159
+ var I18N_FIELD_MAP = {
160
+ title_i18n: "title",
161
+ subtitle_i18n: "subtitle",
162
+ description_i18n: "description",
163
+ cover_i18n: "cover"
164
+ };
165
+ function applyI18nToProducts(products, locale) {
166
+ if (!locale)
167
+ return products;
168
+ return products.map((product) => {
169
+ let patched = null;
170
+ for (const [i18nKey, originalKey] of Object.entries(I18N_FIELD_MAP)) {
171
+ const i18nDict = product[i18nKey];
172
+ if (!i18nDict)
173
+ continue;
174
+ const localizedValue = i18nDict[locale];
175
+ if (localizedValue) {
176
+ if (!patched)
177
+ patched = { ...product };
178
+ patched[originalKey] = localizedValue;
179
+ }
180
+ }
181
+ return patched ?? product;
182
+ });
183
+ }
158
184
  var formatDataKey = (data) => {
159
185
  var _a, _b, _c, _d;
160
186
  const _data = {
@@ -258,6 +284,7 @@ function applyDetailValueToProducts(products, context) {
258
284
  // Annotate the CommonJS export names for ESM import in node:
259
285
  0 && (module.exports = {
260
286
  applyDetailValueToProducts,
287
+ applyI18nToProducts,
261
288
  applyPriceDataToProducts,
262
289
  getIsSessionProduct,
263
290
  perfMark
@@ -52,6 +52,7 @@ export interface PisellCore {
52
52
  validateContext: (config: ModuleContextConfig) => boolean;
53
53
  serverOptions?: ServerOptions;
54
54
  server?: any;
55
+ setContext: (ctx: Partial<BusinessContext>) => void;
55
56
  }
56
57
  /**
57
58
  * 业务上下文接口
@@ -59,6 +60,7 @@ export interface PisellCore {
59
60
  export interface BusinessContext {
60
61
  userId?: string;
61
62
  companyId?: string;
63
+ locale?: string;
62
64
  [key: string]: any;
63
65
  }
64
66
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.2.89",
4
+ "version": "2.2.91",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",