@etsoo/appscript 1.3.89 → 1.3.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/__tests__/app/CoreApp.ts +15 -0
- package/lib/cjs/app/CoreApp.d.ts +3 -1
- package/lib/cjs/app/CoreApp.js +5 -2
- package/lib/cjs/app/IApp.d.ts +3 -1
- package/lib/cjs/business/EntityStatus.d.ts +5 -1
- package/lib/cjs/business/EntityStatus.js +5 -1
- package/lib/cjs/business/ShoppingCart.d.ts +285 -0
- package/lib/cjs/business/ShoppingCart.js +306 -0
- package/lib/cjs/i18n/en.json +1 -0
- package/lib/cjs/i18n/zh-Hans.json +1 -0
- package/lib/cjs/i18n/zh-Hant.json +1 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/mjs/app/CoreApp.d.ts +3 -1
- package/lib/mjs/app/CoreApp.js +5 -2
- package/lib/mjs/app/IApp.d.ts +3 -1
- package/lib/mjs/business/EntityStatus.d.ts +5 -1
- package/lib/mjs/business/EntityStatus.js +5 -1
- package/lib/mjs/business/ShoppingCart.d.ts +285 -0
- package/lib/mjs/business/ShoppingCart.js +302 -0
- package/lib/mjs/i18n/en.json +1 -0
- package/lib/mjs/i18n/zh-Hans.json +1 -0
- package/lib/mjs/i18n/zh-Hant.json +1 -0
- package/lib/mjs/index.d.ts +1 -0
- package/lib/mjs/index.js +1 -0
- package/package.json +2 -2
- package/src/app/CoreApp.ts +9 -2
- package/src/app/IApp.ts +3 -1
- package/src/business/EntityStatus.ts +6 -1
- package/src/business/ShoppingCart.ts +524 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/zh-Hans.json +1 -0
- package/src/i18n/zh-Hant.json +1 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { DataTypes, IStorage, NumberUtils, WindowStorage } from '@etsoo/shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shopping cart owner
|
|
5
|
+
* 购物篮所有人
|
|
6
|
+
*/
|
|
7
|
+
export type ShoppingCartOwner = DataTypes.IdNameItem & {
|
|
8
|
+
isSupplier?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shopping cart data
|
|
13
|
+
* 购物篮数据
|
|
14
|
+
*/
|
|
15
|
+
export type ShoppingCartData<T extends ShoppingCartItem> = {
|
|
16
|
+
currency: string;
|
|
17
|
+
owner: ShoppingCartOwner;
|
|
18
|
+
items: T[];
|
|
19
|
+
promotions: ShoppingPromotion[];
|
|
20
|
+
formData?: any;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Shopping promotion
|
|
25
|
+
* 购物促销
|
|
26
|
+
*/
|
|
27
|
+
export type ShoppingPromotion = {
|
|
28
|
+
/**
|
|
29
|
+
* Promotion id
|
|
30
|
+
* 促销编号
|
|
31
|
+
*/
|
|
32
|
+
id: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Promotion title
|
|
36
|
+
* 促销标题
|
|
37
|
+
*/
|
|
38
|
+
title: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Discount amount
|
|
42
|
+
* 折扣金额
|
|
43
|
+
*/
|
|
44
|
+
amount: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shopping cart base item
|
|
49
|
+
* 购物篮基础项目
|
|
50
|
+
*/
|
|
51
|
+
export type ShoppingCartItemBase = {
|
|
52
|
+
/**
|
|
53
|
+
* Product id
|
|
54
|
+
* 产品编号
|
|
55
|
+
*/
|
|
56
|
+
id: DataTypes.IdType;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Product title, default is name
|
|
60
|
+
* 产品标题,默认为name
|
|
61
|
+
*/
|
|
62
|
+
title?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sale price
|
|
66
|
+
* 销售价格
|
|
67
|
+
*/
|
|
68
|
+
price: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Qty
|
|
72
|
+
* 数量
|
|
73
|
+
*/
|
|
74
|
+
qty: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Product level promotions
|
|
78
|
+
* 产品层次促销
|
|
79
|
+
*/
|
|
80
|
+
promotions: ShoppingPromotion[];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Shopping cart item
|
|
85
|
+
* 购物篮项目
|
|
86
|
+
*/
|
|
87
|
+
export type ShoppingCartItem = ShoppingCartItemBase & {
|
|
88
|
+
/**
|
|
89
|
+
* Product name
|
|
90
|
+
* 产品名称
|
|
91
|
+
*/
|
|
92
|
+
name: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Current price for cache
|
|
96
|
+
* 当前缓存价格
|
|
97
|
+
*/
|
|
98
|
+
currentPrice?: number;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Subtotal
|
|
102
|
+
* 小计
|
|
103
|
+
*/
|
|
104
|
+
subtotal: number;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Total discount amount
|
|
108
|
+
* 总折扣金额
|
|
109
|
+
*/
|
|
110
|
+
discount: number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Shopping cart change reason
|
|
115
|
+
* 购物篮改变原因
|
|
116
|
+
*/
|
|
117
|
+
export type ShoppingCartChangeReason =
|
|
118
|
+
| 'add'
|
|
119
|
+
| 'clear'
|
|
120
|
+
| 'remove'
|
|
121
|
+
| 'title'
|
|
122
|
+
| 'update';
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Shopping cart
|
|
126
|
+
* 购物篮
|
|
127
|
+
*/
|
|
128
|
+
export class ShoppingCart<T extends ShoppingCartItem> {
|
|
129
|
+
/**
|
|
130
|
+
* Create identifier key
|
|
131
|
+
* 创建识别键
|
|
132
|
+
* @param currency Currency
|
|
133
|
+
* @returns Result
|
|
134
|
+
*/
|
|
135
|
+
static createKey(currency: string) {
|
|
136
|
+
return `ETSOO-CART-${currency}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear shopping cart
|
|
141
|
+
* 清除购物篮
|
|
142
|
+
* @param identifier Identifier
|
|
143
|
+
* @param storage Storage
|
|
144
|
+
*/
|
|
145
|
+
static clear(identifier: string, storage: IStorage) {
|
|
146
|
+
try {
|
|
147
|
+
storage.setData(identifier, null);
|
|
148
|
+
storage.setPersistedData(identifier, null);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.log('ShoppingCart clear', error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clear shopping cart
|
|
156
|
+
* 清除购物篮
|
|
157
|
+
* @param currency Currency
|
|
158
|
+
* @param storage Storage
|
|
159
|
+
*/
|
|
160
|
+
static clearWith(
|
|
161
|
+
currency: string,
|
|
162
|
+
storage: IStorage = new WindowStorage()
|
|
163
|
+
) {
|
|
164
|
+
const identifier = this.createKey(currency);
|
|
165
|
+
this.clear(identifier, storage);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Owner data
|
|
170
|
+
* 所有者信息
|
|
171
|
+
*/
|
|
172
|
+
owner?: ShoppingCartOwner;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* ISO currency id
|
|
176
|
+
* 标准货币编号
|
|
177
|
+
*/
|
|
178
|
+
readonly currency: string;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Items
|
|
182
|
+
* 项目
|
|
183
|
+
*/
|
|
184
|
+
readonly items: T[];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Order level promotions
|
|
188
|
+
* 订单层面促销
|
|
189
|
+
*/
|
|
190
|
+
readonly promotions: ShoppingPromotion[];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Related form data
|
|
194
|
+
* 关联的表单数据
|
|
195
|
+
*/
|
|
196
|
+
formData: any;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Currency symbol
|
|
200
|
+
* 币种符号
|
|
201
|
+
*/
|
|
202
|
+
readonly symbol: string | undefined;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Cart identifier
|
|
206
|
+
* 购物篮标识
|
|
207
|
+
*/
|
|
208
|
+
private readonly identifier: string;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Lines count
|
|
212
|
+
* 项目数量
|
|
213
|
+
*/
|
|
214
|
+
get lines() {
|
|
215
|
+
return this.items.length;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Total qty
|
|
220
|
+
* 总数量
|
|
221
|
+
*/
|
|
222
|
+
get totalQty() {
|
|
223
|
+
return this.items.map((item) => item.qty).sum();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Total amount
|
|
228
|
+
* 总金额
|
|
229
|
+
*/
|
|
230
|
+
get totalAmount() {
|
|
231
|
+
const subtotal = this.items
|
|
232
|
+
.map((item) => item.subtotal - item.discount)
|
|
233
|
+
.sum();
|
|
234
|
+
|
|
235
|
+
const discount = this.promotions.sum('amount');
|
|
236
|
+
|
|
237
|
+
return subtotal - discount;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Total amount string
|
|
242
|
+
* 总金额字符串
|
|
243
|
+
*/
|
|
244
|
+
get totalAmountStr() {
|
|
245
|
+
return this.formatAmount(this.totalAmount);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Cached prices
|
|
250
|
+
* 缓存的价格
|
|
251
|
+
*/
|
|
252
|
+
private readonly prices: Record<T['id'], number> = {} as any;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Onchange callback
|
|
256
|
+
* 改变时回调
|
|
257
|
+
*/
|
|
258
|
+
onChange?: (reason: ShoppingCartChangeReason, changedItems: T[]) => void;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Constructor
|
|
262
|
+
* 构造函数
|
|
263
|
+
* @param currency Currency ISO code
|
|
264
|
+
* @param storage Data storage
|
|
265
|
+
*/
|
|
266
|
+
constructor(currency: string, storage?: IStorage);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Constructor
|
|
270
|
+
* 构造函数
|
|
271
|
+
* @param state Initialization state
|
|
272
|
+
* @param storage Data storage
|
|
273
|
+
*/
|
|
274
|
+
constructor(state: ShoppingCartData<T>, storage?: IStorage);
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Constructor
|
|
278
|
+
* 构造函数
|
|
279
|
+
* @param currency Currency ISO code
|
|
280
|
+
* @param storage Data storage
|
|
281
|
+
* @param state Initialization state
|
|
282
|
+
*/
|
|
283
|
+
constructor(
|
|
284
|
+
currencyOrState: string | ShoppingCartData<T>,
|
|
285
|
+
private readonly storage: IStorage = new WindowStorage()
|
|
286
|
+
) {
|
|
287
|
+
const isCurrency = typeof currencyOrState === 'string';
|
|
288
|
+
this.currency = isCurrency ? currencyOrState : currencyOrState.currency;
|
|
289
|
+
|
|
290
|
+
const key = ShoppingCart.createKey(this.currency);
|
|
291
|
+
this.identifier = key;
|
|
292
|
+
this.symbol = NumberUtils.getCurrencySymbol(this.currency);
|
|
293
|
+
|
|
294
|
+
let state: ShoppingCartData<T> | undefined;
|
|
295
|
+
if (isCurrency) {
|
|
296
|
+
try {
|
|
297
|
+
state =
|
|
298
|
+
storage.getPersistedObject<ShoppingCartData<T>>(key) ??
|
|
299
|
+
storage.getObject<ShoppingCartData<T>>(key);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.log('ShoppingCart constructor', error);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
state = currencyOrState;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const { owner, items = [], promotions = [], formData } = state ?? {};
|
|
308
|
+
|
|
309
|
+
this.owner = owner;
|
|
310
|
+
this.items = items;
|
|
311
|
+
this.promotions = promotions;
|
|
312
|
+
this.formData = formData;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private doChange(reason: ShoppingCartChangeReason, changedItems: T[]) {
|
|
316
|
+
if (this.onChange) this.onChange(reason, changedItems);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Add item
|
|
321
|
+
* 添加项目
|
|
322
|
+
* @param item New item
|
|
323
|
+
*/
|
|
324
|
+
addItem(item: T) {
|
|
325
|
+
this.addItems([item]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Add items
|
|
330
|
+
* @param items New items
|
|
331
|
+
*/
|
|
332
|
+
addItems(items: T[]) {
|
|
333
|
+
this.items.push(...items);
|
|
334
|
+
this.doChange('add', items);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Cache price
|
|
339
|
+
* @param id Item id
|
|
340
|
+
* @param price Price
|
|
341
|
+
*/
|
|
342
|
+
cachePrice(id: T['id'], price: number) {
|
|
343
|
+
this.prices[id] = price;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Clear storage
|
|
348
|
+
* @param keepOwner Keep owner data
|
|
349
|
+
*/
|
|
350
|
+
clear(keepOwner?: boolean) {
|
|
351
|
+
this.items.length = 0;
|
|
352
|
+
this.promotions.length = 0;
|
|
353
|
+
|
|
354
|
+
if (keepOwner) {
|
|
355
|
+
this.save();
|
|
356
|
+
} else {
|
|
357
|
+
ShoppingCart.clear(this.identifier, this.storage);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.doChange('clear', []);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Format amount
|
|
365
|
+
* @param amount Amount
|
|
366
|
+
* @returns Result
|
|
367
|
+
*/
|
|
368
|
+
formatAmount(amount: number) {
|
|
369
|
+
return NumberUtils.formatMoney(amount, this.currency);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get item
|
|
374
|
+
* @param id Item id
|
|
375
|
+
* @returns Result
|
|
376
|
+
*/
|
|
377
|
+
getItem(id: T['id']) {
|
|
378
|
+
return this.items.find((item) => item.id === id);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Reset
|
|
383
|
+
* @param item Shopping cart item
|
|
384
|
+
*/
|
|
385
|
+
reset(item: ShoppingCartItem) {
|
|
386
|
+
item.discount = 0;
|
|
387
|
+
item.currentPrice = undefined;
|
|
388
|
+
item.promotions = [];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Save cart data
|
|
393
|
+
* @param persisted For persisted storage
|
|
394
|
+
*/
|
|
395
|
+
save(persisted: boolean = true) {
|
|
396
|
+
if (this.owner == null) return;
|
|
397
|
+
|
|
398
|
+
const { currency, owner, items, promotions, formData } = this;
|
|
399
|
+
const data: ShoppingCartData<T> = {
|
|
400
|
+
currency,
|
|
401
|
+
owner,
|
|
402
|
+
items,
|
|
403
|
+
promotions,
|
|
404
|
+
formData
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
if (persisted) {
|
|
409
|
+
this.storage.setPersistedData(this.identifier, data);
|
|
410
|
+
} else {
|
|
411
|
+
this.storage.setData(this.identifier, data);
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.log('ShoppingCart save', error);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return data;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Trigger update
|
|
422
|
+
* 触发更新
|
|
423
|
+
*/
|
|
424
|
+
update() {
|
|
425
|
+
this.doChange('update', []);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Update discount
|
|
430
|
+
* @param item Shopping cart item
|
|
431
|
+
*/
|
|
432
|
+
updateDiscount(item: ShoppingCartItem) {
|
|
433
|
+
item.discount = item.promotions.sum('amount');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Update item
|
|
438
|
+
* 更新项目
|
|
439
|
+
* @param id Product id
|
|
440
|
+
* @param qty Qty
|
|
441
|
+
* @param itemCreator New item creator
|
|
442
|
+
* @returns Updated or not
|
|
443
|
+
*/
|
|
444
|
+
updateItem(
|
|
445
|
+
id: T['id'],
|
|
446
|
+
qty: number | undefined,
|
|
447
|
+
itemCreator?: () => Omit<
|
|
448
|
+
T,
|
|
449
|
+
'id' | 'price' | 'qty' | 'subtotal' | 'discount' | 'promotions'
|
|
450
|
+
>
|
|
451
|
+
) {
|
|
452
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
453
|
+
if (qty == null) {
|
|
454
|
+
// Remove the item
|
|
455
|
+
if (index !== -1) {
|
|
456
|
+
const removedItems = this.items.splice(index, 1);
|
|
457
|
+
this.doChange('remove', removedItems);
|
|
458
|
+
}
|
|
459
|
+
} else if (index === -1) {
|
|
460
|
+
// New
|
|
461
|
+
if (itemCreator) {
|
|
462
|
+
const price = this.prices[id];
|
|
463
|
+
const newItem = {
|
|
464
|
+
...itemCreator(),
|
|
465
|
+
id,
|
|
466
|
+
price,
|
|
467
|
+
qty,
|
|
468
|
+
subtotal: price * qty,
|
|
469
|
+
discount: 0
|
|
470
|
+
} as T;
|
|
471
|
+
this.addItem(newItem);
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
} else {
|
|
475
|
+
// Update
|
|
476
|
+
const item = this.items[index];
|
|
477
|
+
const price = item.price;
|
|
478
|
+
const newItem = {
|
|
479
|
+
...item,
|
|
480
|
+
qty,
|
|
481
|
+
subtotal: price * qty,
|
|
482
|
+
discount: 0
|
|
483
|
+
};
|
|
484
|
+
this.items.splice(index, 1, newItem);
|
|
485
|
+
this.doChange('update', [item, newItem]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Update price
|
|
493
|
+
* @param id Item id
|
|
494
|
+
* @param price New price
|
|
495
|
+
*/
|
|
496
|
+
updatePrice(id: T['id'], price: number) {
|
|
497
|
+
this.cachePrice(id, price);
|
|
498
|
+
|
|
499
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
500
|
+
if (index !== -1) {
|
|
501
|
+
const item = this.items[index];
|
|
502
|
+
const qty = item.qty;
|
|
503
|
+
const newItem = { ...item, price, subtotal: price * qty };
|
|
504
|
+
this.items.splice(index, 1, newItem);
|
|
505
|
+
this.doChange('update', [item, newItem]);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Update title
|
|
511
|
+
* @param id Item id
|
|
512
|
+
* @param title New title
|
|
513
|
+
*/
|
|
514
|
+
updateTitle(id: T['id'], title: string) {
|
|
515
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
516
|
+
if (index !== -1) {
|
|
517
|
+
const item = this.items[index];
|
|
518
|
+
const newItem: T = { ...item, title };
|
|
519
|
+
if (newItem.name === newItem.title) newItem.title = undefined;
|
|
520
|
+
this.items.splice(index, 1, newItem);
|
|
521
|
+
this.doChange('title', [item, newItem]);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
package/src/i18n/en.json
CHANGED
package/src/i18n/zh-Hans.json
CHANGED
package/src/i18n/zh-Hant.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from './business/DataPrivacy';
|
|
|
29
29
|
export * from './business/EntityStatus';
|
|
30
30
|
export * from './business/ProductUnit';
|
|
31
31
|
export * from './business/RepeatOption';
|
|
32
|
+
export * from './business/ShoppingCart';
|
|
32
33
|
|
|
33
34
|
// def
|
|
34
35
|
export * from './def/ListItem';
|