@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.
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +7 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +22 -9
- package/dist/server/modules/products/index.d.ts +16 -11
- package/dist/server/modules/products/index.js +415 -307
- package/dist/server/modules/products/types.d.ts +1 -0
- package/dist/server/utils/product.d.ts +4 -0
- package/dist/server/utils/product.js +34 -0
- package/dist/types/index.d.ts +2 -0
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +4 -0
- package/lib/server/index.d.ts +5 -0
- package/lib/server/index.js +20 -7
- package/lib/server/modules/products/index.d.ts +16 -11
- package/lib/server/modules/products/index.js +140 -40
- package/lib/server/modules/products/types.d.ts +1 -0
- package/lib/server/utils/product.d.ts +4 -0
- package/lib/server/utils/product.js +27 -0
- package/lib/types/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -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);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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/lib/core/index.d.ts
CHANGED
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
|
*/
|
package/lib/server/index.d.ts
CHANGED
|
@@ -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
|
/**
|
package/lib/server/index.js
CHANGED
|
@@ -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
|
|
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
|
-
* @
|
|
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 →
|
|
206
|
+
* - change_types 包含 price → 仅收集变更 IDs(不拉商品数据)
|
|
206
207
|
* - 有 body → body 完整数据直接覆盖本地
|
|
208
|
+
* - 其他 → SSE 增量拉取
|
|
207
209
|
*
|
|
208
|
-
* product_collection / product_category
|
|
210
|
+
* product_collection / product_category:
|
|
209
211
|
* - 按 relation_product_ids SSE 拉取受影响商品
|
|
210
212
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
ids
|
|
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
|
-
|
|
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(
|
|
327
|
+
const processedProducts = await this.applyFormatters(products, context);
|
|
253
328
|
(0, import_product.perfMark)("prepareProducts.applyFormatters", performance.now() - tFormat, {
|
|
254
|
-
count:
|
|
329
|
+
count: products.length,
|
|
255
330
|
formatterCount: this.formatters.length
|
|
256
331
|
});
|
|
257
332
|
this.logInfo("prepareProductsWithPrice 处理完成", {
|
|
258
|
-
|
|
333
|
+
mode: isIncremental ? "incremental" : "full",
|
|
334
|
+
originalProductCount: products.length,
|
|
259
335
|
processedProductCount: processedProducts.length,
|
|
260
336
|
formatterCount: this.formatters.length
|
|
261
337
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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:
|
|
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 →
|
|
916
|
+
* - change_types 包含 price → 仅收集变更 IDs(不拉商品数据)
|
|
842
917
|
* - 有 body → body 完整数据直接覆盖本地
|
|
918
|
+
* - 其他 → SSE 增量拉取
|
|
843
919
|
*
|
|
844
|
-
* product_collection / product_category
|
|
920
|
+
* product_collection / product_category:
|
|
845
921
|
* - 按 relation_product_ids SSE 拉取受影响商品
|
|
846
922
|
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
923
|
-
|
|
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 增量拉取商品数据
|
|
@@ -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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
/**
|