@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
544 lines (542 loc) • 19 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/server/modules/order/index.ts
var order_exports = {};
__export(order_exports, {
OrderModule: () => OrderModule
});
module.exports = __toCommonJS(order_exports);
var import_lodash_es = require("lodash-es");
var import_dayjs = __toESM(require("dayjs"));
var import_BaseModule = require("../../../modules/BaseModule");
var import_types = require("./types");
var INDEXDB_STORE_NAME = "orders";
var ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY = "server_order_last_full_fetch_at";
var OrderModule = class extends import_BaseModule.BaseModule {
constructor(name, version) {
super(name, version);
this.defaultName = "order";
this.defaultVersion = "1.0.0";
this.pendingSyncMessages = [];
this.ORDER_SYNC_DEBOUNCE_MS = 5e3;
// Map<resource_id, OrderId[]> 资源到订单的倒排索引
this.resourceIdIndex = /* @__PURE__ */ new Map();
}
async initialize(core, options) {
var _a, _b;
this.core = core;
this.store = options == null ? void 0 : options.store;
if (!this.store.map)
this.store.map = /* @__PURE__ */ new Map();
const appPlugin = core.getPlugin("app");
const app = appPlugin == null ? void 0 : appPlugin.getApp();
if (app) {
this.dbManager = app.sqlite;
this.logger = app.logger;
this.storage = app.storage;
const coreData = app.data.store.getStore().getDataByModel("core");
if (!((_a = this.store.createdAtQuery) == null ? void 0 : _a.sales_time_between) && (coreData == null ? void 0 : coreData.core)) {
const today = (0, import_dayjs.default)();
if (coreData == null ? void 0 : coreData.core.operating_day_boundary) {
const operatingDayBoundary = coreData.core.operating_day_boundary;
if (operatingDayBoundary.type === "start_time") {
const boundaryTime = String(operatingDayBoundary.time || "").trim();
const boundaryStart = (0, import_dayjs.default)(`${today.format("YYYY-MM-DD")} ${boundaryTime}`);
const boundaryEnd = boundaryStart.add(1, "day");
if (boundaryStart.isValid()) {
this.store.createdAtQuery = {
sales_time_between: [
boundaryStart.format("YYYY-MM-DD HH:mm:ss"),
boundaryEnd.format("YYYY-MM-DD HH:mm:ss")
]
};
} else {
this.store.createdAtQuery = {
sales_time_between: [
today.startOf("day").format("YYYY-MM-DD HH:mm:ss"),
today.endOf("day").format("YYYY-MM-DD HH:mm:ss")
]
};
}
} else {
this.store.createdAtQuery = {
sales_time_between: [
today.startOf("day").format("YYYY-MM-DD HH:mm:ss"),
today.endOf("day").format("YYYY-MM-DD HH:mm:ss")
]
};
}
} else {
this.store.createdAtQuery = {
sales_time_between: [today.startOf("day").format("YYYY-MM-DD HH:mm:ss"), today.endOf("day").format("YYYY-MM-DD HH:mm:ss")]
};
}
}
}
if (Array.isArray((_b = options == null ? void 0 : options.initialState) == null ? void 0 : _b.list)) {
this.store.list = options.initialState.list;
this.syncOrdersMap();
this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list);
} else {
this.store.list = [];
this.store.map = /* @__PURE__ */ new Map();
}
this.initOrderDataSource();
this.setupOrderSync();
this.logInfo("OrderServer模块初始化完成", {
hasDbManager: !!this.dbManager,
hasLogger: !!this.logger,
initialOrderCount: this.store.list.length
});
}
/**
* 记录信息日志
* @param title 日志标题
* @param metadata 日志元数据
*/
logInfo(title, metadata) {
try {
if (this.logger) {
this.logger.addLog({
type: "info",
title: `[OrderServerModule] ${title}`,
metadata: metadata || {}
});
}
} catch {
}
}
/**
* 记录错误日志
* @param title 日志标题
* @param metadata 日志元数据
*/
logError(title, metadata) {
try {
if (this.logger) {
this.logger.addLog({
type: "error",
title: `[OrderServerModule] ${title}`,
metadata: metadata || {}
});
}
} catch {
}
}
async preload() {
const orders = await this.loadOrdersByServer();
if (orders.length === 0)
return;
this.store.list = (0, import_lodash_es.cloneDeep)(orders);
this.syncOrdersMap();
this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list);
}
getOrders() {
return this.store.list;
}
getOrderById(id) {
return this.store.map.get(this.getIdKey(id));
}
/** 按 order_id 查询(推荐) */
getOrderByOrderId(orderId) {
return this.store.map.get(this.getIdKey(orderId));
}
async loadOrdersByServer() {
let orderList = [];
if (this.orderDataSource) {
try {
const data = await this.orderDataSource.run({
http: {
query: this.store.createdAtQuery
}
});
orderList = data || [];
this.markOrderPulledAtNow();
} catch {
orderList = [];
}
}
await this.saveOrdersToSQLite(orderList);
await this.core.effects.emit(import_types.OrderHooks.onOrdersLoaded, orderList);
return orderList;
}
getOrdersByResourceId(resourceId) {
const ids = this.resourceIdIndex.get(String(resourceId)) || [];
return ids.map((id) => this.store.map.get(id)).filter((order) => !!order);
}
getRoutes() {
return [];
}
destroy() {
var _a;
if (this.syncTimer) {
clearTimeout(this.syncTimer);
this.syncTimer = void 0;
}
this.pendingSyncMessages = [];
if ((_a = this.orderDataSource) == null ? void 0 : _a.destroy)
this.orderDataSource.destroy();
super.destroy();
}
syncOrdersMap() {
this.store.map.clear();
this.resourceIdIndex.clear();
for (const order of this.store.list) {
const id = order.order_id;
if (id === void 0 || id === null)
continue;
this.store.map.set(this.getIdKey(id), order);
const resourceIds = this.extractResourceIds(order);
for (const resourceId of resourceIds) {
const key = String(resourceId);
const existing = this.resourceIdIndex.get(key) || [];
if (existing.some((eid) => this.getIdKey(eid) === this.getIdKey(id)))
continue;
existing.push(id);
this.resourceIdIndex.set(key, existing);
}
}
}
extractResourceIds(order) {
const ids = /* @__PURE__ */ new Set();
const visitResources = (resources) => {
if (!Array.isArray(resources))
return;
resources.forEach((resource) => {
const rid = resource == null ? void 0 : resource.relation_id;
if (rid !== void 0 && rid !== null && rid !== "")
ids.add(String(rid));
if (Array.isArray(resource == null ? void 0 : resource.children))
visitResources(resource.children);
});
};
if (Array.isArray(order == null ? void 0 : order.bookings)) {
order.bookings.forEach((booking) => visitResources((booking == null ? void 0 : booking.resources) || []));
}
return [...ids];
}
getOrderDateKey(order) {
var _a, _b, _c, _d;
const candidates = [
order == null ? void 0 : order.schedule_date,
order == null ? void 0 : order.create_date,
order == null ? void 0 : order.created_at,
(_b = (_a = order == null ? void 0 : order.bookings) == null ? void 0 : _a[0]) == null ? void 0 : _b.start_date,
(_d = (_c = order == null ? void 0 : order.bookings) == null ? void 0 : _c[0]) == null ? void 0 : _d.select_date
];
for (const v of candidates) {
if (!v)
continue;
const text = String(v);
if (!text)
continue;
return text.split("T")[0].split(" ")[0];
}
return void 0;
}
/**
* 初始化 OrderDataSource 实例
*/
initOrderDataSource() {
var _a, _b;
const OrderDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.OrderDataSource;
if (!OrderDataSourceClass)
return;
this.orderDataSource = new OrderDataSourceClass();
}
/**
* 初始化 pubsub 订阅,监听订单变更
*/
setupOrderSync() {
if (!this.orderDataSource)
return;
const shouldSkipHttpPull = this.hasPulledOrdersToday();
const syncPayload = {
pubsub: {
callback: (res) => {
const message = this.normalizeOrderSyncMessage(res);
if (!message)
return;
this.pendingSyncMessages.push(message);
if (this.syncTimer)
clearTimeout(this.syncTimer);
this.syncTimer = setTimeout(() => {
this.processOrderSyncMessages();
}, this.ORDER_SYNC_DEBOUNCE_MS);
}
}
};
syncPayload.http = {
query: this.store.createdAtQuery,
skipHttp: shouldSkipHttpPull
};
this.orderDataSource.run(syncPayload).then(() => {
if (!shouldSkipHttpPull)
this.markOrderPulledAtNow();
}).catch(() => {
});
}
/**
* 处理防抖后的同步消息批次
*
* 后端统一发送 change 消息,不再区分新增/编辑/删除。
* - 单条(id/order_id):若携带 body/data 则直接 upsert 到本地
* - 批量(ids):通过 HTTP 按 ids 增量拉取,再 merge 到本地
*/
async processOrderSyncMessages() {
var _a;
const messages = [...this.pendingSyncMessages];
this.pendingSyncMessages = [];
if (messages.length === 0)
return;
const upsertOrders = /* @__PURE__ */ new Map();
const refreshIds = [];
for (const msg of messages) {
const hasBatchIds = Array.isArray(msg.ids) && msg.ids.length > 0;
const singleId = msg.id ?? msg.order_id;
const hasSingleId = !hasBatchIds && singleId !== void 0 && singleId !== null;
const rawBody = msg.body || msg.data;
const normalizedBody = this.normalizeOrderSyncPayload(rawBody);
if (hasSingleId && normalizedBody) {
upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody);
continue;
}
if (hasBatchIds)
refreshIds.push(...msg.ids);
else if (hasSingleId)
refreshIds.push(singleId);
else if ((_a = msg.relation_order_ids) == null ? void 0 : _a.length)
refreshIds.push(...msg.relation_order_ids);
}
const uniqueRefreshIds = this.uniqueOrderIds(refreshIds);
const upsertList = [...upsertOrders.values()];
if (upsertList.length > 0)
await this.mergeOrdersToStore(upsertList);
if (uniqueRefreshIds.length > 0) {
const freshOrders = await this.fetchOrdersByHttp(uniqueRefreshIds);
if (freshOrders.length > 0)
await this.mergeOrdersToStore(freshOrders);
}
if (upsertList.length === 0 && uniqueRefreshIds.length === 0)
return;
await this.core.effects.emit(import_types.OrderHooks.onOrdersSyncCompleted, null);
}
/**
* 通过 HTTP 按 ids 增量拉取订单
*/
async fetchOrdersByHttp(ids) {
if (!this.orderDataSource || ids.length === 0)
return [];
try {
if (typeof this.orderDataSource.fetchOrdersByIds === "function") {
const orderList2 = await this.orderDataSource.fetchOrdersByIds(ids);
return orderList2 || [];
}
const orderList = await this.orderDataSource.run({
http: {
query: { ids }
}
});
return orderList || [];
} catch {
return [];
}
}
/**
* 将增量订单合并到 store
*/
async mergeOrdersToStore(freshOrders) {
const freshMap = /* @__PURE__ */ new Map();
for (const order of freshOrders) {
const id = order == null ? void 0 : order.order_id;
if (id === void 0 || id === null)
continue;
freshMap.set(this.getIdKey(id), order);
}
const updatedList = this.store.list.map((order) => {
const id = order.order_id;
if (id === void 0 || id === null)
return order;
const key = this.getIdKey(id);
if (!freshMap.has(key))
return order;
const fresh = freshMap.get(key);
freshMap.delete(key);
return fresh;
});
for (const order of freshMap.values()) {
updatedList.push(order);
}
this.store.list = updatedList;
this.syncOrdersMap();
await this.saveOrdersToSQLite(this.store.list);
this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list);
}
normalizeOrderSyncMessage(res) {
const data = (res == null ? void 0 : res.data) || res;
if (!data)
return null;
const payload = (data == null ? void 0 : data.data) && typeof data.data === "object" ? data.data : void 0;
const normalized = {
...data,
_channelKey: data.module || "sales_sync"
};
if (!normalized.type && typeof data.event === "string")
normalized.type = data.event;
if (!normalized.id && data.id !== void 0)
normalized.id = data.id;
if (!normalized.id && data.order_id !== void 0)
normalized.id = data.order_id;
if (!normalized.order_id && data.order_id !== void 0)
normalized.order_id = data.order_id;
if (!normalized.order_id && (payload == null ? void 0 : payload.order_id) !== void 0)
normalized.order_id = payload.order_id;
if (!normalized.body && (payload == null ? void 0 : payload.order_id) !== void 0)
normalized.body = payload;
if (!normalized.data && (payload == null ? void 0 : payload.order_id) !== void 0)
normalized.data = payload;
return normalized;
}
normalizeOrderSyncPayload(payload) {
if (!payload)
return null;
const orderId = payload.order_id;
if (orderId === void 0 || orderId === null)
return null;
return { ...payload, order_id: orderId };
}
uniqueOrderIds(ids) {
const idMap = /* @__PURE__ */ new Map();
for (const id of ids) {
idMap.set(this.getIdKey(id), id);
}
return [...idMap.values()];
}
getIdKey(id) {
return String(id);
}
/**
* 当前订单拉取窗口:
* - 优先使用 createdAtQuery.sales_time_between(已按营业日边界初始化)
* - 无法解析时退化为自然日窗口
*/
getCurrentPullWindow() {
var _a, _b;
const now = (0, import_dayjs.default)();
const range = (_b = (_a = this.store) == null ? void 0 : _a.createdAtQuery) == null ? void 0 : _b.sales_time_between;
if (Array.isArray(range) && range.length >= 2) {
const startAt2 = (0, import_dayjs.default)(range[0]);
const endExclusiveAt = (0, import_dayjs.default)(range[1]);
if (startAt2.isValid() && endExclusiveAt.isValid() && endExclusiveAt.isAfter(startAt2)) {
return { startAt: startAt2, endExclusiveAt };
}
}
const startAt = now.startOf("day");
return { startAt, endExclusiveAt: startAt.add(1, "day") };
}
hasPulledOrdersToday() {
const lastFullFetchAt = this.getStorageItem(ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY);
if (!lastFullFetchAt)
return false;
const parsed = (0, import_dayjs.default)(lastFullFetchAt);
if (!parsed.isValid())
return false;
const { startAt, endExclusiveAt } = this.getCurrentPullWindow();
return (parsed.isAfter(startAt) || parsed.isSame(startAt)) && parsed.isBefore(endExclusiveAt);
}
markOrderPulledAtNow() {
this.setStorageItem(
ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY,
(0, import_dayjs.default)().format("YYYY-MM-DD HH:mm:ss")
);
}
getStorageItem(key) {
var _a;
try {
if ((_a = this.storage) == null ? void 0 : _a.getStorage)
return this.storage.getStorage(key);
if (typeof globalThis !== "undefined" && (globalThis == null ? void 0 : globalThis.localStorage)) {
return globalThis.localStorage.getItem(key);
}
} catch {
}
return null;
}
setStorageItem(key, value) {
var _a;
try {
if ((_a = this.storage) == null ? void 0 : _a.setStorage) {
this.storage.setStorage(key, value);
return;
}
if (typeof globalThis !== "undefined" && (globalThis == null ? void 0 : globalThis.localStorage)) {
globalThis.localStorage.setItem(key, value);
}
} catch {
}
}
async loadOrdersFromSQLite() {
if (!this.dbManager)
return [];
try {
const orders = await this.dbManager.getAll(INDEXDB_STORE_NAME);
return orders || [];
} catch {
return [];
}
}
async saveOrdersToSQLite(orderList) {
if (!this.dbManager)
return;
try {
await this.dbManager.clear(INDEXDB_STORE_NAME);
if (orderList.length === 0)
return;
await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, orderList);
} catch (error) {
this.logError("保存订单到 SQLite 失败", {
error: error instanceof Error ? error.message : String(error),
orderList: orderList.length
});
}
}
async clear() {
this.store.list = [];
this.store.map = /* @__PURE__ */ new Map();
this.resourceIdIndex.clear();
if (this.dbManager) {
await this.dbManager.clear(INDEXDB_STORE_NAME);
}
this.setStorageItem(ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY, "");
this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list);
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
OrderModule
});