@etsoo/appscript 1.5.63 → 1.5.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.github/workflows/main.yml +6 -5
  2. package/README.md +97 -71
  3. package/__tests__/app/CoreApp.ts +141 -143
  4. package/__tests__/app/Culture.ts +23 -23
  5. package/__tests__/app/TestApp.ts +107 -105
  6. package/__tests__/business/BusinessUtils.ts +37 -37
  7. package/__tests__/result/ActionResult.ts +14 -14
  8. package/__tests__/tsconfig.json +16 -16
  9. package/babel.config.json +8 -8
  10. package/lib/cjs/address/AddressAutocomplete.d.ts +1 -1
  11. package/lib/cjs/address/AddressPlace.d.ts +3 -3
  12. package/lib/cjs/address/AddressPlaceBase.d.ts +1 -1
  13. package/lib/cjs/address/AddressRegion.d.ts +2 -2
  14. package/lib/cjs/address/AddressRegion.js +12 -12
  15. package/lib/cjs/address/AddressUtils.d.ts +1 -1
  16. package/lib/cjs/api/AuthApi.d.ts +14 -14
  17. package/lib/cjs/api/AuthApi.js +17 -17
  18. package/lib/cjs/api/BaseApi.d.ts +1 -1
  19. package/lib/cjs/api/EntityApi.d.ts +8 -8
  20. package/lib/cjs/api/EntityApi.js +4 -4
  21. package/lib/cjs/api/dto/AuditLineDto.d.ts +24 -0
  22. package/lib/cjs/api/dto/AuditLineDto.js +2 -0
  23. package/lib/cjs/api/dto/IdLabelDto.d.ts +1 -1
  24. package/lib/cjs/api/dto/IdLabelPrimaryDto.d.ts +1 -1
  25. package/lib/cjs/api/dto/PinDto.d.ts +61 -0
  26. package/lib/cjs/api/dto/PinDto.js +2 -0
  27. package/lib/cjs/api/dto/ResultPayload.d.ts +2 -2
  28. package/lib/cjs/api/rq/AuthRequest.d.ts +1 -1
  29. package/lib/cjs/api/rq/LoginRQ.d.ts +2 -2
  30. package/lib/cjs/api/rq/MergeRQ.d.ts +1 -1
  31. package/lib/cjs/api/rq/QueryRQ.d.ts +2 -2
  32. package/lib/cjs/api/rq/StatusQueryRQ.d.ts +3 -3
  33. package/lib/cjs/api/rq/UpdateModel.d.ts +2 -2
  34. package/lib/cjs/api/rq/UpdateStatusRQ.d.ts +2 -2
  35. package/lib/cjs/app/AppSettings.d.ts +3 -3
  36. package/lib/cjs/bridges/FlutterHost.d.ts +2 -2
  37. package/lib/cjs/bridges/FlutterHost.js +11 -11
  38. package/lib/cjs/business/BusinessTax.js +6 -6
  39. package/lib/cjs/business/BusinessUtils.d.ts +2 -2
  40. package/lib/cjs/business/BusinessUtils.js +5 -5
  41. package/lib/cjs/business/Currency.d.ts +1 -1
  42. package/lib/cjs/business/Currency.js +10 -10
  43. package/lib/cjs/business/ProductUnit.d.ts +1 -1
  44. package/lib/cjs/business/ShoppingCart.d.ts +9 -9
  45. package/lib/cjs/business/ShoppingCart.js +11 -11
  46. package/lib/cjs/custom/CustomField.d.ts +1 -1
  47. package/lib/cjs/custom/CustomFieldData.d.ts +2 -2
  48. package/lib/cjs/i18n/Culture.d.ts +31 -0
  49. package/lib/cjs/i18n/Culture.js +51 -0
  50. package/lib/cjs/index.d.ts +3 -4
  51. package/lib/cjs/index.js +3 -4
  52. package/lib/cjs/result/ActionResult.d.ts +1 -1
  53. package/lib/cjs/result/ActionResultError.d.ts +1 -1
  54. package/lib/cjs/result/ActionResultError.js +3 -3
  55. package/lib/cjs/result/InitCallResult.d.ts +1 -1
  56. package/lib/cjs/state/Culture.d.ts +2 -2
  57. package/lib/cjs/state/User.d.ts +1 -1
  58. package/lib/mjs/address/AddressAutocomplete.d.ts +1 -1
  59. package/lib/mjs/address/AddressPlace.d.ts +3 -3
  60. package/lib/mjs/address/AddressPlaceBase.d.ts +1 -1
  61. package/lib/mjs/address/AddressRegion.d.ts +2 -2
  62. package/lib/mjs/address/AddressRegion.js +13 -13
  63. package/lib/mjs/address/AddressUtils.d.ts +1 -1
  64. package/lib/mjs/address/AddressUtils.js +1 -1
  65. package/lib/mjs/api/AuthApi.d.ts +14 -14
  66. package/lib/mjs/api/AuthApi.js +19 -19
  67. package/lib/mjs/api/BaseApi.d.ts +1 -1
  68. package/lib/mjs/api/EntityApi.d.ts +8 -8
  69. package/lib/mjs/api/EntityApi.js +5 -5
  70. package/lib/mjs/api/dto/AuditLineDto.d.ts +24 -0
  71. package/lib/mjs/api/dto/AuditLineDto.js +1 -0
  72. package/lib/mjs/api/dto/IdLabelDto.d.ts +1 -1
  73. package/lib/mjs/api/dto/IdLabelPrimaryDto.d.ts +1 -1
  74. package/lib/mjs/api/dto/PinDto.d.ts +61 -0
  75. package/lib/mjs/api/dto/PinDto.js +1 -0
  76. package/lib/mjs/api/dto/ResultPayload.d.ts +2 -2
  77. package/lib/mjs/api/rq/AuthRequest.d.ts +1 -1
  78. package/lib/mjs/api/rq/LoginRQ.d.ts +2 -2
  79. package/lib/mjs/api/rq/MergeRQ.d.ts +1 -1
  80. package/lib/mjs/api/rq/QueryRQ.d.ts +2 -2
  81. package/lib/mjs/api/rq/StatusQueryRQ.d.ts +3 -3
  82. package/lib/mjs/api/rq/UpdateModel.d.ts +2 -2
  83. package/lib/mjs/api/rq/UpdateStatusRQ.d.ts +2 -2
  84. package/lib/mjs/app/AppSettings.d.ts +3 -3
  85. package/lib/mjs/bridges/FlutterHost.d.ts +2 -2
  86. package/lib/mjs/bridges/FlutterHost.js +13 -13
  87. package/lib/mjs/business/BusinessTax.js +6 -6
  88. package/lib/mjs/business/BusinessUtils.d.ts +2 -2
  89. package/lib/mjs/business/BusinessUtils.js +5 -5
  90. package/lib/mjs/business/Currency.d.ts +1 -1
  91. package/lib/mjs/business/Currency.js +10 -10
  92. package/lib/mjs/business/ProductUnit.d.ts +1 -1
  93. package/lib/mjs/business/ProductUnit.js +1 -1
  94. package/lib/mjs/business/ShoppingCart.d.ts +9 -9
  95. package/lib/mjs/business/ShoppingCart.js +12 -12
  96. package/lib/mjs/custom/CustomField.d.ts +1 -1
  97. package/lib/mjs/custom/CustomFieldData.d.ts +2 -2
  98. package/lib/mjs/i18n/Culture.d.ts +31 -0
  99. package/lib/mjs/i18n/Culture.js +48 -0
  100. package/lib/mjs/index.d.ts +3 -4
  101. package/lib/mjs/index.js +3 -4
  102. package/lib/mjs/result/ActionResult.d.ts +1 -1
  103. package/lib/mjs/result/ActionResult.js +1 -1
  104. package/lib/mjs/result/ActionResultError.d.ts +1 -1
  105. package/lib/mjs/result/ActionResultError.js +3 -3
  106. package/lib/mjs/result/InitCallResult.d.ts +1 -1
  107. package/lib/mjs/state/Culture.d.ts +2 -2
  108. package/lib/mjs/state/User.d.ts +1 -1
  109. package/package.json +1 -1
  110. package/src/address/AddressAutocomplete.ts +16 -16
  111. package/src/address/AddressCity.ts +12 -12
  112. package/src/address/AddressContinent.ts +35 -35
  113. package/src/address/AddressDistrict.ts +12 -12
  114. package/src/address/AddressLocation.ts +8 -8
  115. package/src/address/AddressPlace.ts +23 -23
  116. package/src/address/AddressPlaceBase.ts +31 -31
  117. package/src/address/AddressRegion.ts +286 -286
  118. package/src/address/AddressState.ts +12 -12
  119. package/src/address/AddressUtils.ts +27 -27
  120. package/src/api/AuthApi.ts +194 -194
  121. package/src/api/BaseApi.ts +13 -13
  122. package/src/api/EntityApi.ts +181 -194
  123. package/src/api/dto/AntiforgeryRequestToken.ts +15 -15
  124. package/src/api/dto/ApiRefreshTokenDto.ts +16 -16
  125. package/src/api/dto/AuditLineDto.ts +27 -0
  126. package/src/api/dto/IdLabelDto.ts +3 -3
  127. package/src/api/dto/IdLabelPrimaryDto.ts +7 -7
  128. package/src/api/dto/InitCallDto.ts +12 -12
  129. package/src/api/dto/PinDto.ts +71 -0
  130. package/src/api/dto/ResultPayload.ts +6 -6
  131. package/src/api/rq/ApiRefreshTokenRQ.ts +8 -8
  132. package/src/api/rq/AuthRequest.ts +40 -40
  133. package/src/api/rq/GetLogInUrlRQ.ts +8 -8
  134. package/src/api/rq/LoginIdRQ.ts +12 -12
  135. package/src/api/rq/LoginRQ.ts +18 -18
  136. package/src/api/rq/MergeRQ.ts +13 -13
  137. package/src/api/rq/QueryPagingData.ts +16 -16
  138. package/src/api/rq/QueryRQ.ts +22 -22
  139. package/src/api/rq/RefreshTokenRQ.ts +4 -4
  140. package/src/api/rq/ResetPasswordRQ.ts +20 -20
  141. package/src/api/rq/SignoutRQ.ts +8 -8
  142. package/src/api/rq/StatusQueryRQ.ts +11 -11
  143. package/src/api/rq/SwitchOrgRQ.ts +8 -8
  144. package/src/api/rq/TokenRQ.ts +4 -4
  145. package/src/api/rq/UpdateModel.ts +12 -12
  146. package/src/api/rq/UpdateStatusRQ.ts +10 -10
  147. package/src/app/AppSettings.ts +27 -27
  148. package/src/app/UserRole.ts +44 -44
  149. package/src/bridges/FlutterHost.ts +92 -97
  150. package/src/bridges/IBridgeHost.ts +49 -51
  151. package/src/business/ApiService.ts +25 -25
  152. package/src/business/BusinessTax.ts +63 -63
  153. package/src/business/BusinessUtils.ts +75 -75
  154. package/src/business/CultureItem.ts +43 -43
  155. package/src/business/Currency.ts +11 -11
  156. package/src/business/DataPrivacy.ts +35 -35
  157. package/src/business/EntityStatus.ts +44 -44
  158. package/src/business/ProductUnit.ts +58 -58
  159. package/src/business/RepeatOption.ts +50 -50
  160. package/src/business/ShoppingCart.ts +696 -706
  161. package/src/custom/CustomField.ts +20 -20
  162. package/src/custom/CustomFieldData.ts +43 -43
  163. package/src/def/ListItem.ts +12 -12
  164. package/src/i18n/Culture.ts +60 -0
  165. package/src/i18n/en.json +244 -244
  166. package/src/i18n/zh-Hans.json +244 -244
  167. package/src/i18n/zh-Hant.json +244 -244
  168. package/src/index.ts +3 -4
  169. package/src/result/ActionResult.ts +19 -19
  170. package/src/result/ActionResultError.ts +30 -30
  171. package/src/result/InitCallResult.ts +21 -21
  172. package/src/state/Culture.ts +3 -3
  173. package/src/state/State.ts +5 -5
  174. package/src/state/User.ts +92 -92
  175. package/tsconfig.cjs.json +17 -17
  176. package/tsconfig.json +17 -17
  177. package/lib/cjs/i18n/CultureUtils.d.ts +0 -13
  178. package/lib/cjs/i18n/CultureUtils.js +0 -32
  179. package/lib/cjs/i18n/en.d.ts +0 -6
  180. package/lib/cjs/i18n/en.js +0 -12
  181. package/lib/cjs/i18n/zhHans.d.ts +0 -6
  182. package/lib/cjs/i18n/zhHans.js +0 -12
  183. package/lib/cjs/i18n/zhHant.d.ts +0 -6
  184. package/lib/cjs/i18n/zhHant.js +0 -12
  185. package/lib/mjs/i18n/CultureUtils.d.ts +0 -13
  186. package/lib/mjs/i18n/CultureUtils.js +0 -29
  187. package/lib/mjs/i18n/en.d.ts +0 -6
  188. package/lib/mjs/i18n/en.js +0 -8
  189. package/lib/mjs/i18n/zhHans.d.ts +0 -6
  190. package/lib/mjs/i18n/zhHans.js +0 -8
  191. package/lib/mjs/i18n/zhHant.d.ts +0 -6
  192. package/lib/mjs/i18n/zhHant.js +0 -8
  193. package/src/i18n/CultureUtils.ts +0 -36
  194. package/src/i18n/en.ts +0 -10
  195. package/src/i18n/zhHans.ts +0 -10
  196. package/src/i18n/zhHant.ts +0 -10
@@ -1,19 +1,19 @@
1
1
  import {
2
- DataTypes,
3
- IStorage,
4
- IdType,
5
- NumberUtils,
6
- WindowStorage
7
- } from '@etsoo/shared';
8
- import { Currency } from './Currency';
2
+ DataTypes,
3
+ IStorage,
4
+ IdType,
5
+ NumberUtils,
6
+ WindowStorage
7
+ } from "@etsoo/shared";
8
+ import { Currency } from "./Currency";
9
9
 
10
10
  /**
11
11
  * Shopping cart owner
12
12
  * 购物篮所有人
13
13
  */
14
14
  export type ShoppingCartOwner = DataTypes.IdNameItem & {
15
- culture?: string;
16
- currency?: Currency;
15
+ culture?: string;
16
+ currency?: Currency;
17
17
  };
18
18
 
19
19
  /**
@@ -21,13 +21,13 @@ export type ShoppingCartOwner = DataTypes.IdNameItem & {
21
21
  * 购物篮数据
22
22
  */
23
23
  export type ShoppingCartData<T extends ShoppingCartItem> = {
24
- culture: string;
25
- currency: Currency;
26
- owner: ShoppingCartOwner;
27
- items: T[];
28
- promotions: ShoppingPromotion[];
29
- formData?: any;
30
- cache?: Record<string, unknown>;
24
+ culture: string;
25
+ currency: Currency;
26
+ owner: ShoppingCartOwner;
27
+ items: T[];
28
+ promotions: ShoppingPromotion[];
29
+ formData?: any;
30
+ cache?: Record<string, unknown>;
31
31
  };
32
32
 
33
33
  /**
@@ -35,23 +35,23 @@ export type ShoppingCartData<T extends ShoppingCartItem> = {
35
35
  * 购物促销
36
36
  */
37
37
  export type ShoppingPromotion = {
38
- /**
39
- * Promotion id
40
- * 促销编号
41
- */
42
- id: number;
43
-
44
- /**
45
- * Promotion title
46
- * 促销标题
47
- */
48
- title: string;
49
-
50
- /**
51
- * Discount amount
52
- * 折扣金额
53
- */
54
- amount: number;
38
+ /**
39
+ * Promotion id
40
+ * 促销编号
41
+ */
42
+ id: number;
43
+
44
+ /**
45
+ * Promotion title
46
+ * 促销标题
47
+ */
48
+ title: string;
49
+
50
+ /**
51
+ * Discount amount
52
+ * 折扣金额
53
+ */
54
+ amount: number;
55
55
  };
56
56
 
57
57
  /**
@@ -59,40 +59,40 @@ export type ShoppingPromotion = {
59
59
  * 购物篮基础项目
60
60
  */
61
61
  export type ShoppingCartItemBase = {
62
- /**
63
- * Product id
64
- * 产品编号
65
- */
66
- id: IdType;
67
-
68
- /**
69
- * Product title, default is name
70
- * 产品标题,默认为name
71
- */
72
- title?: string;
73
-
74
- /**
75
- * Sale price
76
- * 销售价格
77
- */
78
- price: number;
79
-
80
- /**
81
- * Qty
82
- * 数量
83
- */
84
- qty: number;
85
-
86
- /**
87
- * Asset qty
88
- */
89
- assetQty?: number;
90
-
91
- /**
92
- * Product level promotions
93
- * 产品层次促销
94
- */
95
- promotions: ShoppingPromotion[];
62
+ /**
63
+ * Product id
64
+ * 产品编号
65
+ */
66
+ id: IdType;
67
+
68
+ /**
69
+ * Product title, default is name
70
+ * 产品标题,默认为name
71
+ */
72
+ title?: string;
73
+
74
+ /**
75
+ * Sale price
76
+ * 销售价格
77
+ */
78
+ price: number;
79
+
80
+ /**
81
+ * Qty
82
+ * 数量
83
+ */
84
+ qty: number;
85
+
86
+ /**
87
+ * Asset qty
88
+ */
89
+ assetQty?: number;
90
+
91
+ /**
92
+ * Product level promotions
93
+ * 产品层次促销
94
+ */
95
+ promotions: ShoppingPromotion[];
96
96
  };
97
97
 
98
98
  /**
@@ -100,29 +100,29 @@ export type ShoppingCartItemBase = {
100
100
  * 购物篮项目
101
101
  */
102
102
  export type ShoppingCartItem = ShoppingCartItemBase & {
103
- /**
104
- * Product name
105
- * 产品名称
106
- */
107
- name: string;
108
-
109
- /**
110
- * Current price for cache
111
- * 当前缓存价格
112
- */
113
- currentPrice?: number;
114
-
115
- /**
116
- * Subtotal
117
- * 小计
118
- */
119
- subtotal: number;
120
-
121
- /**
122
- * Total discount amount
123
- * 总折扣金额
124
- */
125
- discount: number;
103
+ /**
104
+ * Product name
105
+ * 产品名称
106
+ */
107
+ name: string;
108
+
109
+ /**
110
+ * Current price for cache
111
+ * 当前缓存价格
112
+ */
113
+ currentPrice?: number;
114
+
115
+ /**
116
+ * Subtotal
117
+ * 小计
118
+ */
119
+ subtotal: number;
120
+
121
+ /**
122
+ * Total discount amount
123
+ * 总折扣金额
124
+ */
125
+ discount: number;
126
126
  };
127
127
 
128
128
  /**
@@ -130,628 +130,618 @@ export type ShoppingCartItem = ShoppingCartItemBase & {
130
130
  * 购物篮改变原因
131
131
  */
132
132
  export type ShoppingCartChangeReason =
133
- | 'add'
134
- | 'clear'
135
- | 'remove'
136
- | 'title'
137
- | 'update';
133
+ | "add"
134
+ | "clear"
135
+ | "remove"
136
+ | "title"
137
+ | "update";
138
138
 
139
- const ShoppingCartKeyField = 'ETSOO-CART-KEYS';
139
+ const ShoppingCartKeyField = "ETSOO-CART-KEYS";
140
140
 
141
141
  /**
142
142
  * Shopping cart
143
143
  * 购物篮
144
144
  */
145
145
  export class ShoppingCart<T extends ShoppingCartItem> {
146
- /**
147
- * Create identifier key
148
- * 创建识别键
149
- * @param currency Currency
150
- * @param culture Culture
151
- * @param key Additional key
152
- * @returns Result
153
- */
154
- static createKey(currency: Currency, culture: string, key: string) {
155
- return `ETSOO-CART-${culture}-${key}-${currency}`;
156
- }
157
-
158
- /**
159
- * Clear shopping cart
160
- * 清除购物篮
161
- * @param identifier Identifier
162
- * @param storage Storage
163
- */
164
- static clear(identifier: string, storage: IStorage) {
165
- try {
166
- storage.setData(identifier, null);
167
- storage.setPersistedData(identifier, null);
168
- } catch (error) {
169
- console.warn(`ShoppingCart clear ${identifier} error`, error);
170
- }
171
- }
172
-
173
- /**
174
- * Get cart data
175
- * 获取购物篮数据
176
- * @param storage Storage
177
- * @param id Cart id
178
- * @returns Result
179
- */
180
- static getCartData<D extends ShoppingCartItem>(
181
- storage: IStorage,
182
- id: string
183
- ) {
184
- try {
185
- return (
186
- storage.getPersistedObject<ShoppingCartData<D>>(id) ??
187
- storage.getObject<ShoppingCartData<D>>(id)
188
- );
189
- } catch (error) {
190
- console.warn(`ShoppingCart getCartData ${id} error`, error);
191
- }
192
- }
193
-
194
- /**
195
- * Owner data
196
- * 所有者信息
197
- */
198
- owner?: ShoppingCartOwner;
199
-
200
- _currency!: Currency;
201
-
202
- /**
203
- * ISO currency id
204
- * 标准货币编号
205
- */
206
- get currency() {
207
- return this._currency;
208
- }
209
- private set currency(value: Currency) {
210
- this._currency = value;
211
- }
212
-
213
- _culture!: string;
214
- /**
215
- * ISO culture id, like zh-Hans
216
- * 标准语言文化编号
217
- */
218
- get culture() {
219
- return this._culture;
220
- }
221
- private set culture(value: string) {
222
- this._culture = value;
223
- }
224
-
225
- _items: T[] = [];
226
-
227
- /**
228
- * Items
229
- * 项目
230
- */
231
- get items() {
232
- return this._items;
233
- }
234
- private set items(value) {
235
- this._items = value;
236
- }
237
-
238
- _promotions: ShoppingPromotion[] = [];
239
- /**
240
- * Order level promotions
241
- * 订单层面促销
242
- */
243
- get promotions() {
244
- return this._promotions;
245
- }
246
- private set promotions(value) {
247
- this._promotions = value;
248
- }
249
-
250
- /**
251
- * Related form data
252
- * 关联的表单数据
253
- */
254
- formData: any;
255
-
256
- /**
257
- * Cache
258
- * 缓存对象
259
- */
260
- cache?: Record<string, unknown>;
261
-
262
- _symbol: string | undefined;
263
- /**
264
- * Currency symbol
265
- * 币种符号
266
- */
267
- get symbol() {
268
- return this._symbol;
269
- }
270
- private set symbol(value: string | undefined) {
271
- this._symbol = value;
272
- }
273
-
274
- /**
275
- * Key for identifier
276
- */
277
- readonly key: string;
278
-
279
- /**
280
- * Cart identifier
281
- * 购物篮标识
282
- */
283
- get identifier() {
284
- const o = this.owner;
285
- return ShoppingCart.createKey(this.currency, this.culture, this.key);
286
- }
287
-
288
- /**
289
- * All data keys
290
- * 所有的数据键
291
- */
292
- get keys() {
293
- return this.storage.getPersistedData<string[]>(
294
- ShoppingCartKeyField,
295
- []
296
- );
297
- }
298
- set keys(items: string[]) {
299
- this.storage.setPersistedData(ShoppingCartKeyField, items);
300
- }
301
-
302
- /**
303
- * Lines count
304
- * 项目数量
305
- */
306
- get lines() {
307
- return this.items.length;
308
- }
309
-
310
- /**
311
- * Total qty
312
- * 总数量
313
- */
314
- get totalQty() {
315
- return this.items.map((item) => item.qty).sum();
316
- }
317
-
318
- /**
319
- * Total amount
320
- * 总金额
321
- */
322
- get totalAmount() {
323
- const subtotal = this.items
324
- .map((item) => item.subtotal - item.discount)
325
- .sum();
326
-
327
- const discount = this.promotions.sum('amount');
328
-
329
- return subtotal - discount;
330
- }
331
-
332
- /**
333
- * Total amount string
334
- * 总金额字符串
335
- */
336
- get totalAmountStr() {
337
- return this.formatAmount(this.totalAmount);
338
- }
339
-
340
- /**
341
- * Cached prices
342
- * 缓存的价格
343
- */
344
- private prices = <Record<T['id'], number>>{};
345
-
346
- /**
347
- * Onchange callback
348
- * 改变时回调
349
- */
350
- onChange?: (reason: ShoppingCartChangeReason, changedItems: T[]) => void;
351
-
352
- /**
353
- * Constructor
354
- * 构造函数
355
- * @param key Key for identifier
356
- * @param init Currency & culture ISO code array
357
- * @param storage Data storage
358
- */
359
- constructor(key: string, init: [Currency, string], storage?: IStorage);
360
-
361
- /**
362
- * Constructor
363
- * 构造函数
364
- * @param key Key for identifier
365
- * @param state Initialization state
366
- * @param storage Data storage
367
- */
368
- constructor(key: string, state: ShoppingCartData<T>, storage?: IStorage);
369
-
370
- /**
371
- * Constructor
372
- * 构造函数
373
- * @param key Key for identifier
374
- * @param currency Currency ISO code
375
- * @param storage Data storage
376
- */
377
- constructor(
378
- key: string,
379
- currencyOrState: [Currency, string] | ShoppingCartData<T>,
380
- private readonly storage: IStorage = new WindowStorage()
381
- ) {
382
- this.key = key;
383
-
384
- if (Array.isArray(currencyOrState)) {
385
- this.reset(currencyOrState[0], currencyOrState[1]);
386
- } else {
387
- this.setCartData(currencyOrState);
388
- this.changeCurrency(currencyOrState.currency);
389
- this.changeCulture(currencyOrState.culture);
390
- }
391
- }
392
- private getCartData(): ShoppingCartData<T> | undefined {
393
- return (
394
- this.storage.getPersistedObject(this.identifier) ??
395
- this.storage.getObject(this.identifier)
396
- );
397
- }
398
-
399
- private setCartData(state: ShoppingCartData<T> | undefined) {
400
- const {
401
- owner,
402
- items = [],
403
- promotions = [],
404
- formData,
405
- cache
406
- } = state ?? {};
407
- this.owner = owner;
408
- this.items = items;
409
- this.promotions = promotions;
410
- this.formData = formData;
411
- this.cache = cache;
412
- }
413
-
414
- private doChange(reason: ShoppingCartChangeReason, changedItems: T[]) {
415
- if (this.onChange) this.onChange(reason, changedItems);
416
- }
417
-
418
- /**
419
- * Add item
420
- * 添加项目
421
- * @param item New item
422
- */
423
- addItem(item: T) {
424
- this.addItems([item]);
425
- }
426
-
427
- /**
428
- * Add items
429
- * @param items New items
430
- */
431
- addItems(items: T[]) {
432
- this.items.push(...items);
433
- this.doChange('add', items);
434
- }
435
-
436
- /**
437
- * Cache price
438
- * @param id Item id
439
- * @param price Price
440
- * @param overrideExisting Override existing price
441
- */
442
- cachePrice(id: T['id'], price: number, overrideExisting: boolean = false) {
443
- if (overrideExisting || this.prices[id] == null)
444
- this.prices[id] = price;
445
- }
446
-
447
- /**
448
- * Change currency
449
- * @param currency Currency
450
- */
451
- changeCurrency(currency: Currency) {
452
- this.currency = currency;
453
- this.symbol = NumberUtils.getCurrencySymbol(this.currency);
454
- }
455
-
456
- /**
457
- * Change culture
458
- * @param culture Culture
459
- */
460
- changeCulture(culture: string) {
461
- this.culture = culture;
462
- }
463
-
464
- /**
465
- * Clear storage
466
- * @param keepOwner Keep owner data
467
- */
468
- clear(keepOwner?: boolean) {
469
- this.items.length = 0;
470
- this.promotions.length = 0;
471
- this.prices = <Record<T['id'], number>>{};
472
- this.cache = undefined;
473
-
474
- if (keepOwner) {
475
- this.save();
476
- } else {
477
- ShoppingCart.clear(this.identifier, this.storage);
478
- this.keys.remove(this.identifier);
479
- }
480
-
481
- this.doChange('clear', []);
482
- }
483
-
484
- /**
485
- * Format amount
486
- * @param amount Amount
487
- * @returns Result
488
- */
489
- formatAmount(amount: number) {
490
- return NumberUtils.formatMoney(amount, this.currency);
491
- }
492
-
493
- /**
494
- * Get item
495
- * @param id Item id
496
- * @returns Result
497
- */
498
- getItem(id: T['id']) {
499
- return this.items.find((item) => item.id === id);
500
- }
501
-
502
- /**
503
- * Push item
504
- * 推送项目
505
- * @param data Item data
506
- * @returns Added or not
507
- */
508
- pushItem(data: T) {
509
- if (this.items.some((item) => item.id === data.id)) {
510
- return false;
511
- } else {
512
- this.addItem(data);
513
- return true;
514
- }
515
- }
516
-
517
- /**
518
- * Reset currency and culture
519
- * @param currency New currency
520
- * @param culture New culture
521
- */
522
- reset(currency: Currency, culture: string) {
523
- this.changeCurrency(currency);
524
- this.changeCulture(culture);
525
- this.setCartData(this.getCartData());
526
- }
527
-
528
- /**
529
- * Remove item from the index
530
- * @param index Item index
531
- */
532
- removeItem(index: number) {
533
- const removedItems = this.items.splice(index, 1);
534
- this.doChange('remove', removedItems);
535
- }
536
-
537
- /**
538
- * Reset item
539
- * @param item Shopping cart item
540
- */
541
- resetItem(item: ShoppingCartItem) {
542
- item.discount = 0;
543
- item.currentPrice = undefined;
544
- item.promotions = [];
545
- }
546
-
547
- /**
548
- * Save cart data
549
- * @param persisted For persisted storage
550
- */
551
- save(persisted: boolean = true) {
552
- if (this.owner == null) return;
553
-
554
- const { currency, culture, owner, items, promotions, formData, cache } =
555
- this;
556
- const data: ShoppingCartData<T> = {
557
- currency,
558
- culture,
559
- owner,
560
- items,
561
- promotions,
562
- formData,
563
- cache
564
- };
565
-
566
- try {
567
- if (persisted) {
568
- this.storage.setPersistedData(this.identifier, data);
569
-
570
- const keys = this.keys;
571
- if (!keys.includes(this.identifier)) {
572
- keys.push(this.identifier);
573
- this.keys = keys;
574
- }
575
- } else {
576
- this.storage.setData(this.identifier, data);
577
- }
578
- } catch (error) {
579
- console.warn(`ShoppingCart save ${this.identifier} error`, error);
580
- }
581
-
582
- return data;
583
- }
584
-
585
- /**
586
- * Trigger update
587
- * 触发更新
588
- */
589
- update() {
590
- this.doChange('update', []);
591
- }
592
-
593
- /**
594
- * Update discount
595
- * @param item Shopping cart item
596
- */
597
- updateDiscount(item: ShoppingCartItem) {
598
- item.discount = item.promotions.sum('amount');
599
- }
600
-
601
- /**
602
- * Update asset item
603
- * 更新资产项目
604
- * @param id Product id
605
- * @param qty Asset qty
606
- * @param itemCreator New item creator
607
- * @returns Updated or not
608
- */
609
- updateAssetItem(
610
- id: T['id'],
611
- assetQty: number | undefined,
612
- itemCreator?: () => Omit<
613
- T,
614
- 'id' | 'price' | 'assetQty' | 'subtotal' | 'discount' | 'promotions'
615
- >
616
- ) {
617
- if (assetQty == null || assetQty <= 0) assetQty = 1;
618
-
619
- const index = this.items.findIndex((item) => item.id === id);
620
- if (index === -1) {
621
- // New
622
- if (itemCreator) {
623
- const price = this.prices[id];
624
- const data = itemCreator();
625
- const qty = data.qty;
626
- const newItem = {
627
- ...data,
628
- id,
629
- price,
630
- assetQty,
631
- subtotal: price * qty * assetQty,
632
- discount: 0,
633
- promotions: Array<ShoppingPromotion>()
634
- } as T;
635
- this.addItem(newItem);
636
- }
637
- return false;
638
- } else {
639
- // Update
640
- const item = this.items[index];
641
-
642
- // Price may be cached first
643
- const price = this.prices[id] ?? item.price;
644
- const qty = item.qty;
645
-
646
- const newItem = {
647
- ...item,
648
- price,
649
- assetQty,
650
- subtotal: price * qty * assetQty,
651
- discount: 0
652
- };
653
- this.items.splice(index, 1, newItem);
654
- this.doChange('update', [item, newItem]);
146
+ /**
147
+ * Create identifier key
148
+ * 创建识别键
149
+ * @param currency Currency
150
+ * @param culture Culture
151
+ * @param key Additional key
152
+ * @returns Result
153
+ */
154
+ static createKey(currency: Currency, culture: string, key: string) {
155
+ return `ETSOO-CART-${culture}-${key}-${currency}`;
156
+ }
157
+
158
+ /**
159
+ * Clear shopping cart
160
+ * 清除购物篮
161
+ * @param identifier Identifier
162
+ * @param storage Storage
163
+ */
164
+ static clear(identifier: string, storage: IStorage) {
165
+ try {
166
+ storage.setData(identifier, null);
167
+ storage.setPersistedData(identifier, null);
168
+ } catch (error) {
169
+ console.warn(`ShoppingCart clear ${identifier} error`, error);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Get cart data
175
+ * 获取购物篮数据
176
+ * @param storage Storage
177
+ * @param id Cart id
178
+ * @returns Result
179
+ */
180
+ static getCartData<D extends ShoppingCartItem>(
181
+ storage: IStorage,
182
+ id: string
183
+ ) {
184
+ try {
185
+ return (
186
+ storage.getPersistedObject<ShoppingCartData<D>>(id) ??
187
+ storage.getObject<ShoppingCartData<D>>(id)
188
+ );
189
+ } catch (error) {
190
+ console.warn(`ShoppingCart getCartData ${id} error`, error);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Owner data
196
+ * 所有者信息
197
+ */
198
+ owner?: ShoppingCartOwner;
199
+
200
+ _currency!: Currency;
201
+
202
+ /**
203
+ * ISO currency id
204
+ * 标准货币编号
205
+ */
206
+ get currency() {
207
+ return this._currency;
208
+ }
209
+ private set currency(value: Currency) {
210
+ this._currency = value;
211
+ }
212
+
213
+ _culture!: string;
214
+ /**
215
+ * ISO culture id, like zh-Hans
216
+ * 标准语言文化编号
217
+ */
218
+ get culture() {
219
+ return this._culture;
220
+ }
221
+ private set culture(value: string) {
222
+ this._culture = value;
223
+ }
224
+
225
+ _items: T[] = [];
226
+
227
+ /**
228
+ * Items
229
+ * 项目
230
+ */
231
+ get items() {
232
+ return this._items;
233
+ }
234
+ private set items(value) {
235
+ this._items = value;
236
+ }
237
+
238
+ _promotions: ShoppingPromotion[] = [];
239
+ /**
240
+ * Order level promotions
241
+ * 订单层面促销
242
+ */
243
+ get promotions() {
244
+ return this._promotions;
245
+ }
246
+ private set promotions(value) {
247
+ this._promotions = value;
248
+ }
249
+
250
+ /**
251
+ * Related form data
252
+ * 关联的表单数据
253
+ */
254
+ formData: any;
255
+
256
+ /**
257
+ * Cache
258
+ * 缓存对象
259
+ */
260
+ cache?: Record<string, unknown>;
261
+
262
+ _symbol: string | undefined;
263
+ /**
264
+ * Currency symbol
265
+ * 币种符号
266
+ */
267
+ get symbol() {
268
+ return this._symbol;
269
+ }
270
+ private set symbol(value: string | undefined) {
271
+ this._symbol = value;
272
+ }
273
+
274
+ /**
275
+ * Key for identifier
276
+ */
277
+ readonly key: string;
278
+
279
+ /**
280
+ * Cart identifier
281
+ * 购物篮标识
282
+ */
283
+ get identifier() {
284
+ const o = this.owner;
285
+ return ShoppingCart.createKey(this.currency, this.culture, this.key);
286
+ }
287
+
288
+ /**
289
+ * All data keys
290
+ * 所有的数据键
291
+ */
292
+ get keys() {
293
+ return this.storage.getPersistedData<string[]>(ShoppingCartKeyField, []);
294
+ }
295
+ set keys(items: string[]) {
296
+ this.storage.setPersistedData(ShoppingCartKeyField, items);
297
+ }
298
+
299
+ /**
300
+ * Lines count
301
+ * 项目数量
302
+ */
303
+ get lines() {
304
+ return this.items.length;
305
+ }
306
+
307
+ /**
308
+ * Total qty
309
+ * 总数量
310
+ */
311
+ get totalQty() {
312
+ return this.items.map((item) => item.qty).sum();
313
+ }
314
+
315
+ /**
316
+ * Total amount
317
+ * 总金额
318
+ */
319
+ get totalAmount() {
320
+ const subtotal = this.items
321
+ .map((item) => item.subtotal - item.discount)
322
+ .sum();
323
+
324
+ const discount = this.promotions.sum("amount");
325
+
326
+ return subtotal - discount;
327
+ }
328
+
329
+ /**
330
+ * Total amount string
331
+ * 总金额字符串
332
+ */
333
+ get totalAmountStr() {
334
+ return this.formatAmount(this.totalAmount);
335
+ }
336
+
337
+ /**
338
+ * Cached prices
339
+ * 缓存的价格
340
+ */
341
+ private prices = <Record<T["id"], number>>{};
342
+
343
+ /**
344
+ * Onchange callback
345
+ * 改变时回调
346
+ */
347
+ onChange?: (reason: ShoppingCartChangeReason, changedItems: T[]) => void;
348
+
349
+ /**
350
+ * Constructor
351
+ * 构造函数
352
+ * @param key Key for identifier
353
+ * @param init Currency & culture ISO code array
354
+ * @param storage Data storage
355
+ */
356
+ constructor(key: string, init: [Currency, string], storage?: IStorage);
357
+
358
+ /**
359
+ * Constructor
360
+ * 构造函数
361
+ * @param key Key for identifier
362
+ * @param state Initialization state
363
+ * @param storage Data storage
364
+ */
365
+ constructor(key: string, state: ShoppingCartData<T>, storage?: IStorage);
366
+
367
+ /**
368
+ * Constructor
369
+ * 构造函数
370
+ * @param key Key for identifier
371
+ * @param currency Currency ISO code
372
+ * @param storage Data storage
373
+ */
374
+ constructor(
375
+ key: string,
376
+ currencyOrState: [Currency, string] | ShoppingCartData<T>,
377
+ private readonly storage: IStorage = new WindowStorage()
378
+ ) {
379
+ this.key = key;
380
+
381
+ if (Array.isArray(currencyOrState)) {
382
+ this.reset(currencyOrState[0], currencyOrState[1]);
383
+ } else {
384
+ this.setCartData(currencyOrState);
385
+ this.changeCurrency(currencyOrState.currency);
386
+ this.changeCulture(currencyOrState.culture);
387
+ }
388
+ }
389
+ private getCartData(): ShoppingCartData<T> | undefined {
390
+ return (
391
+ this.storage.getPersistedObject(this.identifier) ??
392
+ this.storage.getObject(this.identifier)
393
+ );
394
+ }
395
+
396
+ private setCartData(state: ShoppingCartData<T> | undefined) {
397
+ const { owner, items = [], promotions = [], formData, cache } = state ?? {};
398
+ this.owner = owner;
399
+ this.items = items;
400
+ this.promotions = promotions;
401
+ this.formData = formData;
402
+ this.cache = cache;
403
+ }
404
+
405
+ private doChange(reason: ShoppingCartChangeReason, changedItems: T[]) {
406
+ if (this.onChange) this.onChange(reason, changedItems);
407
+ }
408
+
409
+ /**
410
+ * Add item
411
+ * 添加项目
412
+ * @param item New item
413
+ */
414
+ addItem(item: T) {
415
+ this.addItems([item]);
416
+ }
417
+
418
+ /**
419
+ * Add items
420
+ * @param items New items
421
+ */
422
+ addItems(items: T[]) {
423
+ this.items.push(...items);
424
+ this.doChange("add", items);
425
+ }
426
+
427
+ /**
428
+ * Cache price
429
+ * @param id Item id
430
+ * @param price Price
431
+ * @param overrideExisting Override existing price
432
+ */
433
+ cachePrice(id: T["id"], price: number, overrideExisting: boolean = false) {
434
+ if (overrideExisting || this.prices[id] == null) this.prices[id] = price;
435
+ }
436
+
437
+ /**
438
+ * Change currency
439
+ * @param currency Currency
440
+ */
441
+ changeCurrency(currency: Currency) {
442
+ this.currency = currency;
443
+ this.symbol = NumberUtils.getCurrencySymbol(this.currency);
444
+ }
445
+
446
+ /**
447
+ * Change culture
448
+ * @param culture Culture
449
+ */
450
+ changeCulture(culture: string) {
451
+ this.culture = culture;
452
+ }
453
+
454
+ /**
455
+ * Clear storage
456
+ * @param keepOwner Keep owner data
457
+ */
458
+ clear(keepOwner?: boolean) {
459
+ this.items.length = 0;
460
+ this.promotions.length = 0;
461
+ this.prices = <Record<T["id"], number>>{};
462
+ this.cache = undefined;
463
+
464
+ if (keepOwner) {
465
+ this.save();
466
+ } else {
467
+ ShoppingCart.clear(this.identifier, this.storage);
468
+ this.keys.remove(this.identifier);
469
+ }
470
+
471
+ this.doChange("clear", []);
472
+ }
473
+
474
+ /**
475
+ * Format amount
476
+ * @param amount Amount
477
+ * @returns Result
478
+ */
479
+ formatAmount(amount: number) {
480
+ return NumberUtils.formatMoney(amount, this.currency);
481
+ }
482
+
483
+ /**
484
+ * Get item
485
+ * @param id Item id
486
+ * @returns Result
487
+ */
488
+ getItem(id: T["id"]) {
489
+ return this.items.find((item) => item.id === id);
490
+ }
491
+
492
+ /**
493
+ * Push item
494
+ * 推送项目
495
+ * @param data Item data
496
+ * @returns Added or not
497
+ */
498
+ pushItem(data: T) {
499
+ if (this.items.some((item) => item.id === data.id)) {
500
+ return false;
501
+ } else {
502
+ this.addItem(data);
503
+ return true;
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Reset currency and culture
509
+ * @param currency New currency
510
+ * @param culture New culture
511
+ */
512
+ reset(currency: Currency, culture: string) {
513
+ this.changeCurrency(currency);
514
+ this.changeCulture(culture);
515
+ this.setCartData(this.getCartData());
516
+ }
517
+
518
+ /**
519
+ * Remove item from the index
520
+ * @param index Item index
521
+ */
522
+ removeItem(index: number) {
523
+ const removedItems = this.items.splice(index, 1);
524
+ this.doChange("remove", removedItems);
525
+ }
526
+
527
+ /**
528
+ * Reset item
529
+ * @param item Shopping cart item
530
+ */
531
+ resetItem(item: ShoppingCartItem) {
532
+ item.discount = 0;
533
+ item.currentPrice = undefined;
534
+ item.promotions = [];
535
+ }
536
+
537
+ /**
538
+ * Save cart data
539
+ * @param persisted For persisted storage
540
+ */
541
+ save(persisted: boolean = true) {
542
+ if (this.owner == null) return;
543
+
544
+ const { currency, culture, owner, items, promotions, formData, cache } =
545
+ this;
546
+ const data: ShoppingCartData<T> = {
547
+ currency,
548
+ culture,
549
+ owner,
550
+ items,
551
+ promotions,
552
+ formData,
553
+ cache
554
+ };
555
+
556
+ try {
557
+ if (persisted) {
558
+ this.storage.setPersistedData(this.identifier, data);
559
+
560
+ const keys = this.keys;
561
+ if (!keys.includes(this.identifier)) {
562
+ keys.push(this.identifier);
563
+ this.keys = keys;
655
564
  }
656
-
657
- return true;
658
- }
659
-
660
- /**
661
- * Update item
662
- * 更新项目
663
- * @param id Product id
664
- * @param qty Qty
665
- * @param itemCreator New item creator
666
- * @returns Updated or not
667
- */
668
- updateItem(
669
- id: T['id'],
670
- qty: number | undefined,
671
- itemCreator?: () => Omit<
672
- T,
673
- 'id' | 'price' | 'qty' | 'subtotal' | 'discount' | 'promotions'
674
- >
675
- ) {
676
- const index = this.items.findIndex((item) => item.id === id);
677
- if (qty == null) {
678
- // Remove the item
679
- if (index !== -1) {
680
- this.removeItem(index);
681
- }
682
- } else if (index === -1) {
683
- // New
684
- if (itemCreator) {
685
- const price = this.prices[id];
686
- const data = itemCreator();
687
- const newItem = {
688
- ...data,
689
- id,
690
- price,
691
- qty,
692
- subtotal: price * qty * (data.assetQty || 1),
693
- discount: 0,
694
- promotions: Array<ShoppingPromotion>()
695
- } as T;
696
- this.addItem(newItem);
697
- }
698
- return false;
699
- } else {
700
- // Update
701
- const item = this.items[index];
702
-
703
- // Price may be cached first
704
- const price = this.prices[id] ?? item.price;
705
- const newItem = {
706
- ...item,
707
- qty,
708
- price,
709
- subtotal: price * qty * (item.assetQty || 1),
710
- discount: 0
711
- };
712
- this.items.splice(index, 1, newItem);
713
- this.doChange('update', [item, newItem]);
714
- }
715
-
716
- return true;
717
- }
718
-
719
- /**
720
- * Update price
721
- * @param id Item id
722
- * @param price New price
723
- */
724
- updatePrice(id: T['id'], price: number) {
725
- this.cachePrice(id, price, true);
726
-
727
- const index = this.items.findIndex((item) => item.id === id);
728
- if (index !== -1) {
729
- const item = this.items[index];
730
- const qty = item.qty;
731
- const assetQty = item.assetQty || 1;
732
- const newItem = {
733
- ...item,
734
- price,
735
- subtotal: price * qty * assetQty
736
- };
737
- this.items.splice(index, 1, newItem);
738
- this.doChange('update', [item, newItem]);
739
- }
740
- }
741
-
742
- /**
743
- * Update title
744
- * @param id Item id
745
- * @param title New title
746
- */
747
- updateTitle(id: T['id'], title: string) {
748
- const index = this.items.findIndex((item) => item.id === id);
749
- if (index !== -1) {
750
- const item = this.items[index];
751
- const newItem: T = { ...item, title };
752
- if (newItem.name === newItem.title) newItem.title = undefined;
753
- this.items.splice(index, 1, newItem);
754
- this.doChange('title', [item, newItem]);
755
- }
756
- }
565
+ } else {
566
+ this.storage.setData(this.identifier, data);
567
+ }
568
+ } catch (error) {
569
+ console.warn(`ShoppingCart save ${this.identifier} error`, error);
570
+ }
571
+
572
+ return data;
573
+ }
574
+
575
+ /**
576
+ * Trigger update
577
+ * 触发更新
578
+ */
579
+ update() {
580
+ this.doChange("update", []);
581
+ }
582
+
583
+ /**
584
+ * Update discount
585
+ * @param item Shopping cart item
586
+ */
587
+ updateDiscount(item: ShoppingCartItem) {
588
+ item.discount = item.promotions.sum("amount");
589
+ }
590
+
591
+ /**
592
+ * Update asset item
593
+ * 更新资产项目
594
+ * @param id Product id
595
+ * @param qty Asset qty
596
+ * @param itemCreator New item creator
597
+ * @returns Updated or not
598
+ */
599
+ updateAssetItem(
600
+ id: T["id"],
601
+ assetQty: number | undefined,
602
+ itemCreator?: () => Omit<
603
+ T,
604
+ "id" | "price" | "assetQty" | "subtotal" | "discount" | "promotions"
605
+ >
606
+ ) {
607
+ if (assetQty == null || assetQty <= 0) assetQty = 1;
608
+
609
+ const index = this.items.findIndex((item) => item.id === id);
610
+ if (index === -1) {
611
+ // New
612
+ if (itemCreator) {
613
+ const price = this.prices[id];
614
+ const data = itemCreator();
615
+ const qty = data.qty;
616
+ const newItem = {
617
+ ...data,
618
+ id,
619
+ price,
620
+ assetQty,
621
+ subtotal: price * qty * assetQty,
622
+ discount: 0,
623
+ promotions: Array<ShoppingPromotion>()
624
+ } as T;
625
+ this.addItem(newItem);
626
+ }
627
+ return false;
628
+ } else {
629
+ // Update
630
+ const item = this.items[index];
631
+
632
+ // Price may be cached first
633
+ const price = this.prices[id] ?? item.price;
634
+ const qty = item.qty;
635
+
636
+ const newItem = {
637
+ ...item,
638
+ price,
639
+ assetQty,
640
+ subtotal: price * qty * assetQty,
641
+ discount: 0
642
+ };
643
+ this.items.splice(index, 1, newItem);
644
+ this.doChange("update", [item, newItem]);
645
+ }
646
+
647
+ return true;
648
+ }
649
+
650
+ /**
651
+ * Update item
652
+ * 更新项目
653
+ * @param id Product id
654
+ * @param qty Qty
655
+ * @param itemCreator New item creator
656
+ * @returns Updated or not
657
+ */
658
+ updateItem(
659
+ id: T["id"],
660
+ qty: number | undefined,
661
+ itemCreator?: () => Omit<
662
+ T,
663
+ "id" | "price" | "qty" | "subtotal" | "discount" | "promotions"
664
+ >
665
+ ) {
666
+ const index = this.items.findIndex((item) => item.id === id);
667
+ if (qty == null) {
668
+ // Remove the item
669
+ if (index !== -1) {
670
+ this.removeItem(index);
671
+ }
672
+ } else if (index === -1) {
673
+ // New
674
+ if (itemCreator) {
675
+ const price = this.prices[id];
676
+ const data = itemCreator();
677
+ const newItem = {
678
+ ...data,
679
+ id,
680
+ price,
681
+ qty,
682
+ subtotal: price * qty * (data.assetQty || 1),
683
+ discount: 0,
684
+ promotions: Array<ShoppingPromotion>()
685
+ } as T;
686
+ this.addItem(newItem);
687
+ }
688
+ return false;
689
+ } else {
690
+ // Update
691
+ const item = this.items[index];
692
+
693
+ // Price may be cached first
694
+ const price = this.prices[id] ?? item.price;
695
+ const newItem = {
696
+ ...item,
697
+ qty,
698
+ price,
699
+ subtotal: price * qty * (item.assetQty || 1),
700
+ discount: 0
701
+ };
702
+ this.items.splice(index, 1, newItem);
703
+ this.doChange("update", [item, newItem]);
704
+ }
705
+
706
+ return true;
707
+ }
708
+
709
+ /**
710
+ * Update price
711
+ * @param id Item id
712
+ * @param price New price
713
+ */
714
+ updatePrice(id: T["id"], price: number) {
715
+ this.cachePrice(id, price, true);
716
+
717
+ const index = this.items.findIndex((item) => item.id === id);
718
+ if (index !== -1) {
719
+ const item = this.items[index];
720
+ const qty = item.qty;
721
+ const assetQty = item.assetQty || 1;
722
+ const newItem = {
723
+ ...item,
724
+ price,
725
+ subtotal: price * qty * assetQty
726
+ };
727
+ this.items.splice(index, 1, newItem);
728
+ this.doChange("update", [item, newItem]);
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Update title
734
+ * @param id Item id
735
+ * @param title New title
736
+ */
737
+ updateTitle(id: T["id"], title: string) {
738
+ const index = this.items.findIndex((item) => item.id === id);
739
+ if (index !== -1) {
740
+ const item = this.items[index];
741
+ const newItem: T = { ...item, title };
742
+ if (newItem.name === newItem.title) newItem.title = undefined;
743
+ this.items.splice(index, 1, newItem);
744
+ this.doChange("title", [item, newItem]);
745
+ }
746
+ }
757
747
  }