ts-glitter 22.4.7 → 22.4.9

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 (98) hide show
  1. package/lib/glitterBundle/Glitter.css +74 -62
  2. package/lowcode/Entry.js +1 -1
  3. package/lowcode/Entry.ts +1 -1
  4. package/lowcode/backend-manager/bg-product.js +49 -32
  5. package/lowcode/backend-manager/bg-product.ts +57 -39
  6. package/lowcode/backend-manager/bg-widget.js +17 -0
  7. package/lowcode/backend-manager/bg-widget.ts +18 -0
  8. package/lowcode/cms-plugin/information/information-module.js +5 -5
  9. package/lowcode/cms-plugin/information/information-module.ts +9 -5
  10. package/lowcode/cms-plugin/menus-setting.js +69 -55
  11. package/lowcode/cms-plugin/menus-setting.ts +77 -61
  12. package/lowcode/cms-plugin/module/form-module.js +109 -89
  13. package/lowcode/cms-plugin/module/form-module.ts +680 -650
  14. package/lowcode/cms-plugin/module/product-excel.js +1 -0
  15. package/lowcode/cms-plugin/module/product-excel.ts +2 -0
  16. package/lowcode/cms-plugin/pos-pages/payment-page.js +28 -10
  17. package/lowcode/cms-plugin/pos-pages/payment-page.ts +29 -10
  18. package/lowcode/cms-plugin/shopping-allowance-manager.js +0 -1
  19. package/lowcode/cms-plugin/shopping-allowance-manager.ts +0 -1
  20. package/lowcode/cms-plugin/shopping-collections.js +367 -193
  21. package/lowcode/cms-plugin/shopping-collections.ts +664 -243
  22. package/lowcode/cms-plugin/shopping-information.js +392 -38
  23. package/lowcode/cms-plugin/shopping-information.ts +479 -87
  24. package/lowcode/cms-plugin/shopping-product-setting.js +2 -2
  25. package/lowcode/cms-plugin/shopping-product-setting.ts +2 -2
  26. package/lowcode/cms-plugin/shopping-setting-advance.js +906 -766
  27. package/lowcode/cms-plugin/shopping-setting-advance.ts +977 -841
  28. package/lowcode/cms-plugin/shopping-setting-basic.js +1547 -1285
  29. package/lowcode/cms-plugin/shopping-setting-basic.ts +1742 -1466
  30. package/lowcode/cms-plugin/stock-stores.js +1 -0
  31. package/lowcode/cms-plugin/stock-stores.ts +1 -0
  32. package/lowcode/cms-plugin/user-list.js +47 -12
  33. package/lowcode/cms-plugin/user-list.ts +52 -14
  34. package/lowcode/css/editor.css +6 -0
  35. package/lowcode/glitterBundle/Glitter.css +74 -62
  36. package/lowcode/jslib/nestable/index.html +317 -0
  37. package/lowcode/jslib/nestable/jquery.nestable.js +484 -0
  38. package/lowcode/official_view_component/form-widget/input-custom.js +98 -6
  39. package/lowcode/official_view_component/form-widget/input-custom.ts +121 -16
  40. package/lowcode/public-components/headers/header-class.js +63 -0
  41. package/lowcode/public-components/headers/header-class.ts +65 -0
  42. package/lowcode/public-components/headers/sy-02.js +386 -400
  43. package/lowcode/public-components/headers/sy-02.ts +482 -492
  44. package/lowcode/public-components/headers/sy-03.js +42 -43
  45. package/lowcode/public-components/headers/sy-03.ts +46 -43
  46. package/lowcode/public-components/headers/sy-04.js +43 -41
  47. package/lowcode/public-components/headers/sy-04.ts +48 -41
  48. package/lowcode/public-components/headers/sy-05.js +30 -27
  49. package/lowcode/public-components/headers/sy-05.ts +33 -27
  50. package/lowcode/public-components/product/product-list.js +160 -148
  51. package/lowcode/public-components/product/product-list.ts +186 -165
  52. package/lowcode/public-models/product.ts +26 -1
  53. package/lowcode/src/glitterBundle/Glitter.css +74 -62
  54. package/package.json +1 -1
  55. package/rxmnt81tnk.json +1 -0
  56. package/src/api-public/controllers/shop.js +10 -4
  57. package/src/api-public/controllers/shop.js.map +1 -1
  58. package/src/api-public/controllers/shop.ts +14 -9
  59. package/src/api-public/services/ezpay/tool.d.ts +1 -0
  60. package/src/api-public/services/mail.js +1 -1
  61. package/src/api-public/services/mail.js.map +1 -1
  62. package/src/api-public/services/mail.ts +1 -1
  63. package/src/api-public/services/schedule.d.ts +0 -1
  64. package/src/api-public/services/schedule.js +12 -35
  65. package/src/api-public/services/schedule.js.map +1 -1
  66. package/src/api-public/services/schedule.ts +15 -39
  67. package/src/api-public/services/shopee.js +7 -17
  68. package/src/api-public/services/shopping.d.ts +27 -6
  69. package/src/api-public/services/shopping.js +364 -85
  70. package/src/api-public/services/shopping.js.map +1 -1
  71. package/src/api-public/services/shopping.ts +510 -101
  72. package/src/api-public/services/updated-table-checked.js +58 -1
  73. package/src/api-public/services/updated-table-checked.js.map +1 -1
  74. package/src/api-public/services/updated-table-checked.ts +62 -1
  75. package/src/api-public/services/user-update.js +14 -0
  76. package/src/api-public/services/user-update.js.map +1 -1
  77. package/src/api-public/services/user-update.ts +15 -0
  78. package/src/api-public/services/user.js +1 -1
  79. package/src/api-public/services/user.js.map +1 -1
  80. package/src/api-public/services/user.ts +1 -1
  81. package/src/app-project/serverless/src/modules/database.d.ts +1 -1
  82. package/src/app-project/serverless/src/modules/redis.d.ts +1 -1
  83. package/src/helper/glitter-util.d.ts +1 -0
  84. package/src/index.js +7 -5
  85. package/src/index.js.map +1 -1
  86. package/src/index.ts +45 -38
  87. package/src/modules/firebase.js +1 -0
  88. package/src/modules/firebase.js.map +1 -1
  89. package/src/modules/firebase.ts +1 -0
  90. package/src/seo-config.d.ts +1 -1
  91. package/src/seo-config.js +1 -2
  92. package/src/seo-config.js.map +1 -1
  93. package/src/seo-config.ts +1 -2
  94. package/src/services/saas-table-check.js.map +1 -1
  95. package/src/services/ses.js +4 -3
  96. package/src/services/ses.js.map +1 -1
  97. package/src/services/system-schedule.js.map +1 -1
  98. package/src/services/system-schedule.ts +1 -0
@@ -6,8 +6,10 @@ import { LanguageLocation } from '../glitter-base/global/language.js';
6
6
  import { QuestionInfo } from './module/question-info.js';
7
7
  import { Tool } from '../modules/tool.js';
8
8
  import { Product, MultiSaleType } from '../public-models/product.js';
9
+ import { ShareDialog } from '../glitterBundle/dialog/ShareDialog.js';
9
10
 
10
11
  const html = String.raw;
12
+ const css = String.raw;
11
13
 
12
14
  export class ShoppingSettingAdvance {
13
15
  public static main(obj: {
@@ -26,23 +28,127 @@ export class ShoppingSettingAdvance {
26
28
  const gvc = obj.gvc;
27
29
  const postMD = obj.postMD;
28
30
  const vm = obj.vm2;
29
-
31
+ const section_ID = {
32
+ tag: gvc.glitter.getUUID(),
33
+ }
30
34
  const categoryTitles: Record<string, string> = {
31
35
  commodity: '商品',
32
36
  course: '課程',
37
+ reserve:'預約'
33
38
  };
39
+ const id = section_ID.tag;
34
40
  const carTitle = categoryTitles[postMD.product_category] || '商品';
41
+ const dialog = new ShareDialog(gvc.glitter);
42
+ function drawReverseSection() {
43
+ if (carTitle == "reserve"){
44
+ return ``
45
+ }
46
+ gvc.addStyle(css`
47
+ .available-input{
48
+ height: 40px;
49
+ padding: 0 18px;
50
+ flex: 1 0 0;
51
+ gap: 10px;
52
+ border-radius: 10px;
53
+ border: 1px solid #DDD;
54
+ box-sizing: border-box;
55
+ }
56
+ `)
57
+ postMD.available_time = postMD.available_time ?? {
58
+ earliest: {
59
+ value: "1",
60
+ unit: "month",
61
+ },
62
+ latest: {
63
+ value: "1",
64
+ unit: "day",
65
+ },
66
+ }
67
+ return BgWidget.mainCard(html`
68
+ <div class="d-flex flex-column" style="gap: 18px;">
69
+ <div class="tx_700">預約設定</div>
70
+ <div class="d-flex flex-column" style="gap: 8px;">
71
+ <div class="d-flex flex-column" style="gap: 4px">
72
+ <div class="tx_700">最早可預約時間</div>
73
+ <div class="tx_gray_14">最早可接受預約的時間,例如提前1個月</div>
74
+ </div>
75
+ <div class="d-flex " style="gap: 18px;">
76
+ <div class="d-flex align-items-center available-input w-50">
77
+ <input class="border-0 w-100" type="number" onchange="${gvc.event((e)=>{
78
+ if (postMD.available_time.earliest.unit == 'day' ){
79
+ if (Number(e.value) > 30){
80
+ dialog.infoMessage({
81
+ text:"請設定小於30天",
82
+ callback: () => {
83
+ e.value = postMD.available_time.latest.value;
84
+ }
85
+ })
86
+ }
87
+ if (Number(e.value) > Number(postMD.available_time.latest.value)){
88
+ dialog.infoMessage({
89
+ text:"最早可預約時間需早於最晚可預約時間",
90
+ callback: () => {
91
+ e.value = postMD.available_time.latest.value;
92
+ }
93
+ })
94
+ }
95
+ postMD.available_time.earliest.value = e.value;
96
+ }else {
97
+ postMD.available_time.earliest.value = e.value;
98
+ }
35
99
 
36
- return BgWidget.container(
37
- gvc.bindView(() => {
38
- const id = gvc.glitter.getUUID();
39
- return {
40
- bind: id,
41
- view: () => {
42
- return [
43
- BgWidget.mainCard(
44
- [
45
- html`
100
+
101
+ })}">
102
+ </div>
103
+ <div class="w-50" style="box-sizing: border-box">
104
+ ${BgWidget.select({
105
+ default: postMD.available_time.earliest.unit,
106
+ gvc: gvc,
107
+ options: [
108
+ {
109
+ value:"月",
110
+ key:"month"
111
+ },
112
+ {
113
+ value:"日",
114
+ key:"day"
115
+ },
116
+ ],
117
+ callback(value: any): void {
118
+ postMD.available_time.earliest.unit = value;
119
+ },
120
+ })}
121
+ </div>
122
+ </div>
123
+ </div>
124
+ <div >
125
+ <div class="d-flex flex-column" style="gap: 4px;margin-bottom: 8px;">
126
+ <div class="tx_700">最晚可預約時間</div>
127
+ <div class="tx_gray_14">最晚可接受預約的時間,例如提前1天前</div>
128
+ </div>
129
+ <div class="d-flex align-items-center available-input w-100 flex-shrink-0">
130
+ <input class="border-0 w-100" type="number" onchange="${gvc.event((e)=>{
131
+ if (e.value > 30){
132
+ dialog.infoMessage({
133
+ text:"請設定小於30天",
134
+ callback: () => {
135
+ e.value = postMD.available_time.latest.value;
136
+ }
137
+ })
138
+ }else{
139
+ postMD.available_time.latest.value = e.value;
140
+ }
141
+ })}">
142
+ <div class="ms-auto tx_gray_16">天</div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ `)
147
+ }
148
+ function drawTagSection(){
149
+ return BgWidget.mainCard(
150
+ [
151
+ html`
46
152
  <div class="guide5-4">
47
153
  <div class="d-flex align-items-center justify-content-between">
48
154
  <div>
@@ -53,242 +159,244 @@ export class ShoppingSettingAdvance {
53
159
  ${BgWidget.grayNote('用戶於前台搜尋標籤,即可搜尋到此' + carTitle)} ${BgWidget.mbContainer(4)}
54
160
  </div>
55
161
  ${BgWidget.blueNote(
56
- '使用現有標籤',
57
- gvc.event(() => {
58
- BgProduct.useProductTags({
59
- gvc,
60
- config_key: 'product_general_tags',
61
- config_lang: vm.language,
62
- def: (postMD.product_tag.language as any)[vm.language] ?? [],
63
- callback: tags => {
64
- (postMD.product_tag.language as any)[vm.language] = tags;
65
- gvc.notifyDataChange(id);
66
- },
67
- });
68
- })
69
- )}
162
+ '使用現有標籤',
163
+ gvc.event(() => {
164
+ BgProduct.useProductTags({
165
+ gvc,
166
+ config_key: 'product_general_tags',
167
+ config_lang: vm.language,
168
+ def: (postMD.product_tag.language as any)[vm.language] ?? [],
169
+ callback: tags => {
170
+ (postMD.product_tag.language as any)[vm.language] = tags;
171
+ gvc.notifyDataChange(id);
172
+ },
173
+ });
174
+ })
175
+ )}
70
176
  </div>
71
177
  ${BgWidget.multipleInput(gvc, (postMD.product_tag.language as any)[vm.language], {
72
- save: def => {
73
- (postMD.product_tag.language as any)[vm.language] = def;
74
- },
75
- })}
178
+ save: def => {
179
+ (postMD.product_tag.language as any)[vm.language] = def;
180
+ },
181
+ })}
76
182
  </div>
77
183
  `,
78
- html`
184
+ html`
79
185
  <div class="d-flex align-items-center justify-content-between">
80
186
  <div>
81
187
  <div style="color: #393939; font-weight: 700;">${carTitle}管理員標籤</div>
82
188
  ${BgWidget.grayNote('操作後台人員登記與分類用,不會顯示於前台')} ${BgWidget.mbContainer(4)}
83
189
  </div>
84
190
  ${BgWidget.blueNote(
85
- '使用現有標籤',
86
- gvc.event(() => {
87
- BgProduct.useProductTags({
88
- gvc,
89
- config_key: 'product_manager_tags',
90
- def: postMD.product_customize_tag ?? [],
91
- callback: tags => {
92
- postMD.product_customize_tag = tags;
93
- gvc.notifyDataChange(id);
94
- },
95
- });
96
- })
97
- )}
191
+ '使用現有標籤',
192
+ gvc.event(() => {
193
+ BgProduct.useProductTags({
194
+ gvc,
195
+ config_key: 'product_manager_tags',
196
+ def: postMD.product_customize_tag ?? [],
197
+ callback: tags => {
198
+ postMD.product_customize_tag = tags;
199
+ gvc.notifyDataChange(id);
200
+ },
201
+ });
202
+ })
203
+ )}
98
204
  </div>
99
205
  ${BgWidget.multipleInput(
100
- gvc,
101
- postMD.product_customize_tag ?? [],
102
- {
103
- save: def => {
104
- postMD.product_customize_tag = [...new Set(def)];
105
- },
106
- },
107
- true
108
- )}
206
+ gvc,
207
+ postMD.product_customize_tag ?? [],
208
+ {
209
+ save: def => {
210
+ postMD.product_customize_tag = [...new Set(def)];
211
+ },
212
+ },
213
+ true
214
+ )}
109
215
  `,
110
- html` <div class="d-flex align-items-center gap-2">
216
+ html` <div class="d-flex align-items-center gap-2">
111
217
  <div style="color: #393939; font-weight: 700;">${carTitle}促銷標籤</div>
112
218
  ${BgWidget.questionButton(
113
- gvc.event(() => {
114
- QuestionInfo.promoteLabel(gvc);
115
- })
116
- )}
219
+ gvc.event(() => {
220
+ QuestionInfo.promoteLabel(gvc);
221
+ })
222
+ )}
117
223
  </div>
118
224
  ${BgWidget.mbContainer(8)}
119
225
  ${gvc.bindView(
120
- (() => {
121
- const id = gvc.glitter.getUUID();
122
- let options: any[] = [];
226
+ (() => {
227
+ const id = gvc.glitter.getUUID();
228
+ let options: any[] = [];
123
229
 
124
- ApiUser.getPublicConfig('promo-label', 'manager').then(data => {
125
- if (data.result && Array.isArray(data.response?.value)) {
126
- options = [
127
- ...data.response.value.map(({ id, title }: { id: string; title: string }) => ({
128
- key: id,
129
- value: title,
130
- })),
131
- { key: '', value: '不設定' },
132
- ];
133
- }
134
- gvc.notifyDataChange(id);
135
- });
230
+ ApiUser.getPublicConfig('promo-label', 'manager').then(data => {
231
+ if (data.result && Array.isArray(data.response?.value)) {
232
+ options = [
233
+ ...data.response.value.map(({ id, title }: { id: string; title: string }) => ({
234
+ key: id,
235
+ value: title,
236
+ })),
237
+ { key: '', value: '不設定' },
238
+ ];
239
+ }
240
+ gvc.notifyDataChange(id);
241
+ });
136
242
 
137
- return {
138
- bind: id,
139
- view: () => {
140
- return BgWidget.select({
141
- gvc: obj.gvc,
142
- default: postMD.label || '',
143
- options: options,
144
- callback: (text: any) => {
145
- postMD.label = text || undefined;
146
- gvc.notifyDataChange(id);
147
- },
148
- });
149
- },
150
- };
151
- })()
152
- )}`,
153
- postMD.product_category === 'course'
154
- ? ''
155
- : html` <div class="d-flex align-items-center">
243
+ return {
244
+ bind: id,
245
+ view: () => {
246
+ return BgWidget.select({
247
+ gvc: obj.gvc,
248
+ default: postMD.label || '',
249
+ options: options,
250
+ callback: (text: any) => {
251
+ postMD.label = text || undefined;
252
+ gvc.notifyDataChange(id);
253
+ },
254
+ });
255
+ },
256
+ };
257
+ })()
258
+ )}`,
259
+ postMD.product_category === 'course'
260
+ ? ''
261
+ : html` <div class="d-flex align-items-center">
156
262
  <div style="color: #393939; font-weight: 700;">數量單位</div>
157
263
  ${BgWidget.languageInsignia(vm.language, 'margin-left:5px;')}
158
264
  </div>
159
265
  ${BgWidget.grayNote('例如 : 坪、件、個、打,預設單位為件。')}
160
266
  ${BgWidget.editeInput({
161
- gvc: obj.gvc,
162
- default: `${(postMD.unit as any)[vm.language] || ''}`,
163
- title: '',
164
- type: 'text',
165
- placeHolder: '件',
166
- callback: (text: any) => {
167
- (postMD.unit as any)[vm.language] = text;
168
- gvc.notifyDataChange(id);
169
- },
170
- })}`,
171
- html`
267
+ gvc: obj.gvc,
268
+ default: `${(postMD.unit as any)[vm.language] || ''}`,
269
+ title: '',
270
+ type: 'text',
271
+ placeHolder: '件',
272
+ callback: (text: any) => {
273
+ (postMD.unit as any)[vm.language] = text;
274
+ gvc.notifyDataChange(id);
275
+ },
276
+ })}`,
277
+ html`
172
278
  <div class="d-flex align-items-center">
173
279
  <div style="color: #393939; font-weight: 700;">排序權重</div>
174
280
  </div>
175
281
  ${BgWidget.grayNote('數字越大商品排序會越靠前。')}
176
282
  ${BgWidget.editeInput({
177
- gvc: obj.gvc,
178
- default: `${postMD.sort_weight || ''}`,
179
- title: '',
180
- type: 'text',
181
- placeHolder: '數字越大商品排序會越靠前',
182
- callback: (text: any) => {
183
- postMD.sort_weight = text;
184
- gvc.notifyDataChange(id);
185
- },
186
- })}
283
+ gvc: obj.gvc,
284
+ default: `${postMD.sort_weight || ''}`,
285
+ title: '',
286
+ type: 'text',
287
+ placeHolder: '數字越大商品排序會越靠前',
288
+ callback: (text: any) => {
289
+ postMD.sort_weight = text;
290
+ gvc.notifyDataChange(id);
291
+ },
292
+ })}
187
293
  `,
188
- ]
189
- .filter(Boolean)
190
- .join(BgWidget.mbContainer(18))
191
- ),
192
- BgWidget.mainCard(
193
- [
194
- html`
294
+ ]
295
+ .filter(Boolean)
296
+ .join(BgWidget.mbContainer(18))
297
+ )
298
+ }
299
+ function drawBuyLimit() {
300
+ return BgWidget.mainCard(
301
+ [
302
+ html`
195
303
  <div class="d-flex flex-column guide5-4">
196
304
  <div style="font-weight: 700;" class="mb-2">${carTitle}購買限制</div>
197
305
  </div>
198
306
  `,
199
- ...(postMD.productType.giveaway
200
- ? []
201
- : [
202
- {
203
- title: '最低需要購買多少',
204
- key: 'min_qty',
205
- checked: postMD.min_qty,
206
- },
207
- {
208
- title: '最高只能購買多少',
209
- key: 'max_qty',
210
- checked: postMD.max_qty,
211
- },
212
- {
213
- title: `需連同特定${carTitle}一併購買`,
214
- key: 'match_by_with',
215
- checked: postMD.match_by_with,
216
- },
217
- {
218
- title: `過往購買過特定${carTitle}才能購買此${carTitle}`,
219
- key: 'legacy_by_with',
220
- checked: postMD.legacy_by_with,
221
- },
222
- ].map(dd => {
223
- const stringArray = [
224
- BgWidget.inlineCheckBox({
225
- title: '',
226
- gvc: gvc,
227
- def: [dd.checked ? dd.key : ''],
228
- array: [
229
- {
230
- title: dd.title,
231
- value: dd.key,
232
- },
233
- ],
234
- callback: () => {
235
- const handleToggle = (key: string, defaultValue: any) => {
236
- if ((postMD as any)[key]) {
237
- delete (postMD as any)[key];
238
- } else {
239
- (postMD as any)[key] = defaultValue;
240
- }
241
- gvc.notifyDataChange(id);
242
- };
307
+ ...(postMD.productType.giveaway
308
+ ? []
309
+ : [
310
+ {
311
+ title: `最低需要${carTitle}多少`,
312
+ key: 'min_qty',
313
+ checked: postMD.min_qty,
314
+ },
315
+ {
316
+ title: `最高只能${carTitle}多少`,
317
+ key: 'max_qty',
318
+ checked: postMD.max_qty,
319
+ },
320
+ {
321
+ title: `需連同特定${carTitle}一併購買`,
322
+ key: 'match_by_with',
323
+ checked: postMD.match_by_with,
324
+ },
325
+ {
326
+ title: `過往購買過特定${carTitle}才能購買此${carTitle}`,
327
+ key: 'legacy_by_with',
328
+ checked: postMD.legacy_by_with,
329
+ },
330
+ ].map(dd => {
331
+ const stringArray = [
332
+ BgWidget.inlineCheckBox({
333
+ title: '',
334
+ gvc: gvc,
335
+ def: [dd.checked ? dd.key : ''],
336
+ array: [
337
+ {
338
+ title: dd.title,
339
+ value: dd.key,
340
+ },
341
+ ],
342
+ callback: () => {
343
+ const handleToggle = (key: string, defaultValue: any) => {
344
+ if ((postMD as any)[key]) {
345
+ delete (postMD as any)[key];
346
+ } else {
347
+ (postMD as any)[key] = defaultValue;
348
+ }
349
+ gvc.notifyDataChange(id);
350
+ };
243
351
 
244
- switch (dd.key) {
245
- case 'min_qty':
246
- case 'max_qty':
247
- handleToggle(dd.key, 1);
248
- break;
249
- case 'match_by_with':
250
- case 'legacy_by_with':
251
- handleToggle(dd.key, []);
252
- break;
253
- }
254
- },
255
- type: 'multiple',
256
- }),
257
- ];
258
- if (dd.checked) {
259
- switch (dd.key) {
260
- case 'min_qty':
261
- case 'max_qty':
262
- stringArray.push(
263
- html` <div class="d-flex align-items-center fw-500" style="gap:10px;">
352
+ switch (dd.key) {
353
+ case 'min_qty':
354
+ case 'max_qty':
355
+ handleToggle(dd.key, 1);
356
+ break;
357
+ case 'match_by_with':
358
+ case 'legacy_by_with':
359
+ handleToggle(dd.key, []);
360
+ break;
361
+ }
362
+ },
363
+ type: 'multiple',
364
+ }),
365
+ ];
366
+ if (dd.checked) {
367
+ switch (dd.key) {
368
+ case 'min_qty':
369
+ case 'max_qty':
370
+ stringArray.push(
371
+ html` <div class="d-flex align-items-center fw-500" style="gap:10px;">
264
372
  ${BgWidget.editeInput({
265
- gvc: obj.gvc,
266
- default: `${postMD[dd.key] || ''}`,
267
- title: '',
268
- type: 'number',
269
- placeHolder: `1`,
270
- callback: (text: any) => {
271
- (postMD as any)[dd.key] = parseInt(text, 10);
272
- gvc.notifyDataChange(id);
273
- },
274
- })}
373
+ gvc: obj.gvc,
374
+ default: `${postMD[dd.key] || ''}`,
375
+ title: '',
376
+ type: 'number',
377
+ placeHolder: `1`,
378
+ callback: (text: any) => {
379
+ (postMD as any)[dd.key] = parseInt(text, 10);
380
+ gvc.notifyDataChange(id);
381
+ },
382
+ })}
275
383
 
276
384
  </div>`
277
- );
278
- break;
279
- case 'match_by_with':
280
- stringArray.push(
281
- obj.gvc.bindView(() => {
282
- const id = gvc.glitter.getUUID();
283
- return {
284
- bind: id,
285
- view: () => {
286
- try {
287
- return html`
385
+ );
386
+ break;
387
+ case 'match_by_with':
388
+ stringArray.push(
389
+ obj.gvc.bindView(() => {
390
+ const id = gvc.glitter.getUUID();
391
+ return {
392
+ bind: id,
393
+ view: () => {
394
+ try {
395
+ return html`
288
396
  <div style="font-weight: 700;" class=" d-flex flex-column">
289
397
  ${BgWidget.grayNote(
290
- `購物車必須連同包含以下其中一個${postMD.product_category === 'course' ? `課程或商品` : `商品`}才可結帳`
291
- )}
398
+ `購物車必須連同包含以下其中一個${postMD.product_category === 'course' ? `課程或商品` : `商品`}才可結帳`
399
+ )}
292
400
  </div>
293
401
  <div
294
402
  class="d-flex align-items-center gray-bottom-line-18"
@@ -298,95 +406,95 @@ export class ShoppingSettingAdvance {
298
406
  <div class="tx_normal">商品列表</div>
299
407
  </div>
300
408
  ${BgWidget.grayButton(
301
- '選擇商品',
302
- gvc.event(() => {
303
- BgProduct.productsDialog({
304
- gvc: gvc,
305
- default: postMD.match_by_with!,
306
- callback: async value => {
307
- postMD.match_by_with = value;
308
- gvc.notifyDataChange(id);
309
- },
310
- filter: dd => dd.key !== postMD.id,
311
- });
312
- }),
313
- { textStyle: 'font-weight: 400;' }
314
- )}
409
+ '選擇商品',
410
+ gvc.event(() => {
411
+ BgProduct.productsDialog({
412
+ gvc: gvc,
413
+ default: postMD.match_by_with!,
414
+ callback: async value => {
415
+ postMD.match_by_with = value;
416
+ gvc.notifyDataChange(id);
417
+ },
418
+ filter: dd => dd.key !== postMD.id,
419
+ });
420
+ }),
421
+ { textStyle: 'font-weight: 400;' }
422
+ )}
315
423
  </div>
316
424
  ${gvc.bindView(() => {
317
- const vm: {
318
- id: string;
319
- loading: boolean;
320
- data: OptionsItem[];
321
- } = {
322
- id: gvc.glitter.getUUID(),
323
- loading: true,
324
- data: [],
325
- };
326
- BgProduct.getProductOpts(postMD.match_by_with!).then(res => {
327
- vm.data = res;
328
- vm.loading = false;
329
- gvc.notifyDataChange(vm.id);
330
- });
331
- return {
332
- bind: vm.id,
333
- view: async () => {
334
- if (vm.loading) {
335
- return BgWidget.spinner();
336
- }
337
- return vm.data
338
- .map((opt: OptionsItem, index) => {
339
- return html` <div
425
+ const vm: {
426
+ id: string;
427
+ loading: boolean;
428
+ data: OptionsItem[];
429
+ } = {
430
+ id: gvc.glitter.getUUID(),
431
+ loading: true,
432
+ data: [],
433
+ };
434
+ BgProduct.getProductOpts(postMD.match_by_with!).then(res => {
435
+ vm.data = res;
436
+ vm.loading = false;
437
+ gvc.notifyDataChange(vm.id);
438
+ });
439
+ return {
440
+ bind: vm.id,
441
+ view: async () => {
442
+ if (vm.loading) {
443
+ return BgWidget.spinner();
444
+ }
445
+ return vm.data
446
+ .map((opt: OptionsItem, index) => {
447
+ return html` <div
340
448
  class="d-flex align-items-center form-check-label c_updown_label gap-3"
341
449
  >
342
450
  <span class="tx_normal">${index + 1} .</span>
343
451
  ${BgWidget.validImageBox({
344
- gvc: gvc,
345
- image: opt.image,
346
- width: 40,
347
- })}
452
+ gvc: gvc,
453
+ image: opt.image,
454
+ width: 40,
455
+ })}
348
456
  <div class="tx_normal ${opt.note ? 'mb-1' : ''}">
349
457
  ${opt.value}
350
458
  </div>
351
459
  ${opt.note
352
- ? html` <div class="tx_gray_12">${opt.note}</div> `
353
- : ''}
460
+ ? html` <div class="tx_gray_12">${opt.note}</div> `
461
+ : ''}
354
462
  </div>`;
355
- })
356
- .join('');
357
- },
358
- divCreate: {
359
- class: `d-flex py-2 flex-column`,
360
- style: `gap:10px;`,
361
- },
362
- };
363
- })}
463
+ })
464
+ .join('');
465
+ },
466
+ divCreate: {
467
+ class: `d-flex py-2 flex-column`,
468
+ style: `gap:10px;`,
469
+ },
470
+ };
471
+ })}
364
472
  `;
365
- } catch (e) {
366
- console.error(e);
367
- return '';
368
- }
369
- },
370
- divCreate: {
371
- class: `w-100`,
372
- },
373
- };
374
- })
375
- );
376
- break;
377
- case 'legacy_by_with':
378
- stringArray.push(
379
- obj.gvc.bindView(() => {
380
- const id = gvc.glitter.getUUID();
381
- return {
382
- bind: id,
383
- view: () => {
384
- try {
385
- return html`
473
+ } catch (e) {
474
+ console.error(e);
475
+ return '';
476
+ }
477
+ },
478
+ divCreate: {
479
+ class: `w-100`,
480
+ },
481
+ };
482
+ })
483
+ );
484
+ break;
485
+ case 'legacy_by_with':
486
+ stringArray.push(
487
+ obj.gvc.bindView(() => {
488
+ const id = gvc.glitter.getUUID();
489
+ return {
490
+ bind: id,
491
+ view: () => {
492
+ try {
493
+ return html`
386
494
  <div style="font-weight: 700;" class=" d-flex flex-column">
387
495
  ${BgWidget.grayNote(
388
- `已購買過的訂單記錄中,必須包含以下${postMD.product_category === 'course' ? `課程或商品` : `商品`}才可以結帳`
389
- )}
496
+ `已購買過的訂單記錄中,必須包含以下${postMD.product_category === 'course' ? `課程或商品` : `商品`}才可以結帳`
497
+ )}
390
498
  </div>
391
499
  <div
392
500
  class="d-flex align-items-center gray-bottom-line-18"
@@ -396,212 +504,216 @@ export class ShoppingSettingAdvance {
396
504
  <div class="tx_normal">商品列表</div>
397
505
  </div>
398
506
  ${BgWidget.grayButton(
399
- '選擇商品',
400
- gvc.event(() => {
401
- BgProduct.productsDialog({
402
- gvc: gvc,
403
- default: postMD.match_by_with!,
404
- callback: async value => {
405
- postMD.match_by_with = value;
406
- gvc.notifyDataChange(id);
407
- },
408
- filter: dd => dd.key !== postMD.id,
409
- });
410
- }),
411
- { textStyle: 'font-weight: 400;' }
412
- )}
507
+ '選擇商品',
508
+ gvc.event(() => {
509
+ BgProduct.productsDialog({
510
+ gvc: gvc,
511
+ default: postMD.match_by_with!,
512
+ callback: async value => {
513
+ postMD.match_by_with = value;
514
+ gvc.notifyDataChange(id);
515
+ },
516
+ filter: dd => dd.key !== postMD.id,
517
+ });
518
+ }),
519
+ { textStyle: 'font-weight: 400;' }
520
+ )}
413
521
  </div>
414
522
  ${gvc.bindView(() => {
415
- const vm: {
416
- id: string;
417
- loading: boolean;
418
- data: OptionsItem[];
419
- } = {
420
- id: gvc.glitter.getUUID(),
421
- loading: true,
422
- data: [],
423
- };
424
- BgProduct.getProductOpts(postMD.match_by_with!).then(res => {
425
- vm.data = res;
426
- vm.loading = false;
427
- gvc.notifyDataChange(vm.id);
428
- });
429
- return {
430
- bind: vm.id,
431
- view: async () => {
432
- if (vm.loading) {
433
- return BgWidget.spinner();
434
- }
435
- return vm.data
436
- .map((opt: OptionsItem, index) => {
437
- return html` <div
523
+ const vm: {
524
+ id: string;
525
+ loading: boolean;
526
+ data: OptionsItem[];
527
+ } = {
528
+ id: gvc.glitter.getUUID(),
529
+ loading: true,
530
+ data: [],
531
+ };
532
+ BgProduct.getProductOpts(postMD.match_by_with!).then(res => {
533
+ vm.data = res;
534
+ vm.loading = false;
535
+ gvc.notifyDataChange(vm.id);
536
+ });
537
+ return {
538
+ bind: vm.id,
539
+ view: async () => {
540
+ if (vm.loading) {
541
+ return BgWidget.spinner();
542
+ }
543
+ return vm.data
544
+ .map((opt: OptionsItem, index) => {
545
+ return html` <div
438
546
  class="d-flex align-items-center form-check-label c_updown_label gap-3"
439
547
  >
440
548
  <span class="tx_normal">${index + 1} .</span>
441
549
  ${BgWidget.validImageBox({
442
- gvc: gvc,
443
- image: opt.image,
444
- width: 40,
445
- })}
550
+ gvc: gvc,
551
+ image: opt.image,
552
+ width: 40,
553
+ })}
446
554
  <div class="tx_normal ${opt.note ? 'mb-1' : ''}">
447
555
  ${opt.value}
448
556
  </div>
449
557
  ${opt.note
450
- ? html` <div class="tx_gray_12">${opt.note}</div> `
451
- : ''}
558
+ ? html` <div class="tx_gray_12">${opt.note}</div> `
559
+ : ''}
452
560
  </div>`;
453
- })
454
- .join('');
455
- },
456
- divCreate: {
457
- class: `d-flex py-2 flex-column`,
458
- style: `gap:10px;`,
459
- },
460
- };
461
- })}
561
+ })
562
+ .join('');
563
+ },
564
+ divCreate: {
565
+ class: `d-flex py-2 flex-column`,
566
+ style: `gap:10px;`,
567
+ },
568
+ };
569
+ })}
462
570
  `;
463
- } catch (e) {
464
- console.error(e);
465
- return '';
466
- }
467
- },
468
- divCreate: {
469
- class: `w-100`,
470
- },
471
- };
472
- })
473
- );
474
- break;
475
- }
476
- }
477
- return stringArray.join('');
478
- })),
479
- ].join('')
480
- ),
481
- BgWidget.mainCard(
482
- [
483
- html`
571
+ } catch (e) {
572
+ console.error(e);
573
+ return '';
574
+ }
575
+ },
576
+ divCreate: {
577
+ class: `w-100`,
578
+ },
579
+ };
580
+ })
581
+ );
582
+ break;
583
+ }
584
+ }
585
+ return stringArray.join('');
586
+ })),
587
+ ].join('')
588
+ )
589
+ }
590
+ function drawTaxSection() {
591
+ return BgWidget.mainCard(
592
+ [
593
+ html`
484
594
  <div class="d-flex flex-column guide5-4">
485
595
  <div style="font-weight: 700;" class="mb-2">商品稅額</div>
486
596
  </div>
487
597
  `,
488
- BgWidget.select({
489
- gvc: gvc,
490
- callback: text => {
491
- postMD.tax = text;
492
- },
493
- default: postMD.tax ?? '5',
494
- options: [
495
- {
496
- key: '5',
497
- value: '一般稅額(5%)',
498
- },
499
- {
500
- key: '0',
501
- value: '免稅商品(0%)',
502
- },
503
- ],
504
- }),
505
- ].join('')
506
- ),
507
- BgWidget.mainCard(
508
- (() => {
509
- const priceVM = {
510
- id: gvc.glitter.getUUID(),
511
- loading: true,
512
- typeData: [] as { type: MultiSaleType; key: string; name: string }[],
513
- showPriceDetail: false,
514
- };
598
+ BgWidget.select({
599
+ gvc: gvc,
600
+ callback: text => {
601
+ postMD.tax = text;
602
+ },
603
+ default: postMD.tax ?? '5',
604
+ options: [
605
+ {
606
+ key: '5',
607
+ value: '一般稅額(5%)',
608
+ },
609
+ {
610
+ key: '0',
611
+ value: '免稅商品(0%)',
612
+ },
613
+ ],
614
+ }),
615
+ ].join('')
616
+ )
617
+ }
618
+ function drawSpecialPriceSection() {
619
+ return BgWidget.mainCard(
620
+ (() => {
621
+ const priceVM = {
622
+ id: gvc.glitter.getUUID(),
623
+ loading: true,
624
+ typeData: [] as { type: MultiSaleType; key: string; name: string }[],
625
+ showPriceDetail: false,
626
+ };
515
627
 
516
- const isDesktop = document.body.clientWidth > 768;
628
+ const isDesktop = document.body.clientWidth > 768;
517
629
 
518
- const getIndexStyle = (index: number) =>
519
- index === 0
520
- ? `height: 100%; padding: 0; min-width: ${isDesktop ? 250 : 200}px; max-width: ${isDesktop ? 250 : 200}px;position: sticky; left: 0; background: #fff; box-shadow: 1px 0px 0px 0px rgba(0, 0, 0, 0.10);`
521
- : 'height: 100%; padding: 0; text-align: center; justify-content: center; min-width: 126px;';
630
+ const getIndexStyle = (index: number) =>
631
+ index === 0
632
+ ? `height: 100%; padding: 0; min-width: ${isDesktop ? 250 : 200}px; max-width: ${isDesktop ? 250 : 200}px;position: sticky; left: 0; background: #fff; box-shadow: 1px 0px 0px 0px rgba(0, 0, 0, 0.10);`
633
+ : 'height: 100%; padding: 0; text-align: center; justify-content: center; min-width: 126px;';
522
634
 
523
- const resetPostList = (result: string[], type: MultiSaleType) => {
524
- const existingPrices = new Map(
525
- postMD.multi_sale_price
526
- ?.filter(m => {
527
- return m.type === type;
528
- })
529
- .map(m => {
530
- return [m.key, new Map(m.variants.map(v => [v.spec.join(','), v.price]))];
531
- })
532
- );
635
+ const resetPostList = (result: string[], type: MultiSaleType) => {
636
+ const existingPrices = new Map(
637
+ postMD.multi_sale_price
638
+ ?.filter(m => {
639
+ return m.type === type;
640
+ })
641
+ .map(m => {
642
+ return [m.key, new Map(m.variants.map(v => [v.spec.join(','), v.price]))];
643
+ })
644
+ );
533
645
 
534
- postMD.multi_sale_price = [
535
- ...(postMD.multi_sale_price?.filter(item => item.type !== type) ?? []),
536
- ...result.map(key => ({
537
- type,
538
- key,
539
- variants: postMD.variants.map(v => ({
540
- spec: v.spec,
541
- price: existingPrices.get(key)?.get(v.spec.join(',')) ?? 0,
542
- })),
543
- })),
544
- ];
646
+ postMD.multi_sale_price = [
647
+ ...(postMD.multi_sale_price?.filter(item => item.type !== type) ?? []),
648
+ ...result.map(key => ({
649
+ type,
650
+ key,
651
+ variants: postMD.variants.map(v => ({
652
+ spec: v.spec,
653
+ price: existingPrices.get(key)?.get(v.spec.join(',')) ?? 0,
654
+ })),
655
+ })),
656
+ ];
545
657
 
546
- gvc.notifyDataChange(priceVM.id);
547
- };
658
+ gvc.notifyDataChange(priceVM.id);
659
+ };
548
660
 
549
- const toggleObject = (type: MultiSaleType) => {
550
- return {
551
- gvc,
552
- postData: postMD.multi_sale_price
553
- ? postMD.multi_sale_price.filter(item => item.type === type).map(item => item.key)
554
- : [],
555
- callback: (result: string[]) => resetPostList(result, type),
556
- };
557
- };
661
+ const toggleObject = (type: MultiSaleType) => {
662
+ return {
663
+ gvc,
664
+ postData: postMD.multi_sale_price
665
+ ? postMD.multi_sale_price.filter(item => item.type === type).map(item => item.key)
666
+ : [],
667
+ callback: (result: string[]) => resetPostList(result, type),
668
+ };
669
+ };
558
670
 
559
- const createToggleList = () => [
560
- {
561
- title: '會員等級價格開啟',
562
- note: '開啟後即可為各個會員等級設置專屬的價格',
563
- type: 'level',
564
- event: () => {
565
- BgProduct.setMemberPriceSetting(toggleObject('level'));
566
- },
567
- },
568
- {
569
- title: '門市專屬價格開啟',
570
- note: '開啟後即可為各個門市設置專屬的價格',
571
- type: 'store',
572
- event: () => {
573
- BgProduct.setStorePriceSetting(toggleObject('store'));
574
- },
575
- },
576
- {
577
- title: '顧客標籤價格開啟',
578
- note: '開啟後即可為各個顧客標籤設置專屬的價格',
579
- type: 'tags',
580
- event: () => {
581
- BgProduct.setUserTagPriceSetting(toggleObject('tags'));
582
- },
583
- },
584
- ];
671
+ const createToggleList = () => [
672
+ {
673
+ title: '會員等級價格開啟',
674
+ note: '開啟後即可為各個會員等級設置專屬的價格',
675
+ type: 'level',
676
+ event: () => {
677
+ BgProduct.setMemberPriceSetting(toggleObject('level'));
678
+ },
679
+ },
680
+ {
681
+ title: '門市專屬價格開啟',
682
+ note: '開啟後即可為各個門市設置專屬的價格',
683
+ type: 'store',
684
+ event: () => {
685
+ BgProduct.setStorePriceSetting(toggleObject('store'));
686
+ },
687
+ },
688
+ {
689
+ title: '顧客標籤價格開啟',
690
+ note: '開啟後即可為各個顧客標籤設置專屬的價格',
691
+ type: 'tags',
692
+ event: () => {
693
+ BgProduct.setUserTagPriceSetting(toggleObject('tags'));
694
+ },
695
+ },
696
+ ];
585
697
 
586
- return gvc.bindView({
587
- bind: priceVM.id,
588
- view: () => {
589
- if (priceVM.loading) return BgWidget.spinner();
698
+ return gvc.bindView({
699
+ bind: priceVM.id,
700
+ view: () => {
701
+ if (priceVM.loading) return BgWidget.spinner();
590
702
 
591
- const toggleList = createToggleList();
592
- const particularKeys = priceVM.typeData.filter(item => {
593
- return postMD.multi_sale_price?.some(m => m.type === item.type && m.key === item.key);
594
- });
703
+ const toggleList = createToggleList();
704
+ const particularKeys = priceVM.typeData.filter(item => {
705
+ return postMD.multi_sale_price?.some(m => m.type === item.type && m.key === item.key);
706
+ });
595
707
 
596
- try {
597
- return html`
708
+ try {
709
+ return html`
598
710
  <div class="title-container px-0 mb-2">
599
711
  <div style="color:#393939;font-weight: 700;">專屬價格</div>
600
712
  <div class="flex-fill"></div>
601
713
  </div>
602
714
  ${toggleList
603
- .map(
604
- item => html`
715
+ .map(
716
+ item => html`
605
717
  <div class="d-flex align-items-center">
606
718
  <div>
607
719
  <div class="d-flex align-items-center gap-2 mb-1">
@@ -612,15 +724,15 @@ export class ShoppingSettingAdvance {
612
724
  style="cursor: pointer;"
613
725
  type="checkbox"
614
726
  onchange="${gvc.event(e => {
615
- if (e.checked) {
616
- item.event();
617
- } else {
618
- postMD.multi_sale_price = postMD.multi_sale_price?.filter(
619
- m => m.type !== item.type
620
- );
621
- gvc.notifyDataChange(priceVM.id);
622
- }
623
- })}"
727
+ if (e.checked) {
728
+ item.event();
729
+ } else {
730
+ postMD.multi_sale_price = postMD.multi_sale_price?.filter(
731
+ m => m.type !== item.type
732
+ );
733
+ gvc.notifyDataChange(priceVM.id);
734
+ }
735
+ })}"
624
736
  ${postMD.multi_sale_price?.some(m => m.type === item.type) ? `checked` : ''}
625
737
  />
626
738
  </div>
@@ -633,10 +745,10 @@ export class ShoppingSettingAdvance {
633
745
  </div>
634
746
  </div>
635
747
  `
636
- )
637
- .join(`<div class="w-100 my-3 border-top"></div>`)}
748
+ )
749
+ .join(`<div class="w-100 my-3 border-top"></div>`)}
638
750
  ${particularKeys.length > 1
639
- ? html`
751
+ ? html`
640
752
  <div
641
753
  class="d-flex justify-content-start align-items-center mt-2 my-1"
642
754
  style="border-radius: 10px; padding: 10px 20px; background: #F7F7F7;"
@@ -646,282 +758,286 @@ export class ShoppingSettingAdvance {
646
758
  >
647
759
  </div>
648
760
  `
649
- : ''}
761
+ : ''}
650
762
  ${postMD.multi_sale_price && postMD.multi_sale_price.length > 0
651
- ? html` <div class="mt-3 d-grid" style="overflow: scroll;" id="scrollDiv">
763
+ ? html` <div class="mt-3 d-grid" style="overflow: scroll;" id="scrollDiv">
652
764
  <div class="d-flex">
653
765
  ${['商品名稱', '成本', '原價', '售價', ...particularKeys.map(item => item.name)]
654
- .map(
655
- (item, index) => html`
766
+ .map(
767
+ (item, index) => html`
656
768
  <div style="${getIndexStyle(index)}">
657
769
  <div>${item}</div>
658
770
  ${BgWidget.horizontalLine({ margin: '1rem 0 0;' })}
659
771
  </div>
660
772
  `
661
- )
662
- .join('')}
773
+ )
774
+ .join('')}
663
775
  </div>
664
776
  <div class="w-100 d-flex flex-column">
665
777
  ${postMD.variants
666
- .map((variant, index) => {
667
- const { spec, cost = 0, sale_price, preview_image, compare_price } = variant;
778
+ .map((variant, index) => {
779
+ const { spec, cost = 0, sale_price, preview_image, compare_price } = variant;
668
780
 
669
- return html` <div class="d-flex align-items-center">
781
+ return html` <div class="d-flex align-items-center">
670
782
  ${[
671
- [
672
- BgWidget.validImageBox({
673
- gvc,
674
- image: preview_image,
675
- width: 40,
676
- style: 'border-radius: 5px;',
677
- }),
678
- gvc.bindView({
679
- bind: `spec-bar-${index}`,
680
- dataList: [{ obj: priceVM, key: 'showPriceDetail' }],
681
- view: () => {
682
- return html`
783
+ [
784
+ BgWidget.validImageBox({
785
+ gvc,
786
+ image: preview_image,
787
+ width: 40,
788
+ style: 'border-radius: 5px;',
789
+ }),
790
+ gvc.bindView({
791
+ bind: `spec-bar-${index}`,
792
+ dataList: [{ obj: priceVM, key: 'showPriceDetail' }],
793
+ view: () => {
794
+ return html`
683
795
  <div>
684
796
  ${spec && spec.length > 0
685
- ? spec.join(' / ')
686
- : Tool.truncateString(postMD.title, 10)}
797
+ ? spec.join(' / ')
798
+ : Tool.truncateString(postMD.title, 10)}
687
799
  </div>
688
800
  ${priceVM.showPriceDetail
689
- ? html` <div style="color: #8D8D8D;">
801
+ ? html` <div style="color: #8D8D8D;">
690
802
  定價 : ${compare_price.toLocaleString()} / 售價 :
691
803
  ${sale_price.toLocaleString()}
692
804
  </div>`
693
- : ''}
805
+ : ''}
694
806
  `;
695
- },
696
- divCreate: {
697
- class: 'ms-2',
698
- style: 'font-size: 14px;',
699
- },
700
- }),
701
- ].join(''),
702
- `$ ${cost.toLocaleString()}`,
703
- `$ ${compare_price.toLocaleString()}`,
704
- `$ ${sale_price.toLocaleString()}`,
705
- ...particularKeys.map(item =>
706
- gvc.bindView(
707
- (() => {
708
- const id = gvc.glitter.getUUID();
709
- return {
710
- bind: id,
711
- view: () => {
712
- const priceObj = postMD.multi_sale_price?.find(
713
- m => m.type === item.type && m.key === item.key
714
- );
715
- const variantObj = priceObj?.variants.find(
716
- v => v.spec.join(',') === spec.join(',')
717
- );
718
- return BgWidget.editeInput({
719
- gvc,
720
- title: '',
721
- default: `${variantObj?.price ?? 0}`,
722
- placeHolder: '',
723
- callback: value => {
724
- const n = parseInt(`${value ?? 0}`, 10);
725
- if (variantObj && !isNaN(n) && n > 0) {
726
- variantObj.price = n;
727
- }
728
- gvc.notifyDataChange(id);
729
- },
730
- });
731
- },
732
- divCreate: {
733
- style: 'width: 120px;',
734
- },
735
- };
736
- })()
737
- )
738
- ),
739
- ]
740
- .map(
741
- (item, index) => html`
807
+ },
808
+ divCreate: {
809
+ class: 'ms-2',
810
+ style: 'font-size: 14px;',
811
+ },
812
+ }),
813
+ ].join(''),
814
+ `$ ${cost.toLocaleString()}`,
815
+ `$ ${compare_price.toLocaleString()}`,
816
+ `$ ${sale_price.toLocaleString()}`,
817
+ ...particularKeys.map(item =>
818
+ gvc.bindView(
819
+ (() => {
820
+ const id = gvc.glitter.getUUID();
821
+ return {
822
+ bind: id,
823
+ view: () => {
824
+ const priceObj = postMD.multi_sale_price?.find(
825
+ m => m.type === item.type && m.key === item.key
826
+ );
827
+ const variantObj = priceObj?.variants.find(
828
+ v => v.spec.join(',') === spec.join(',')
829
+ );
830
+ return BgWidget.editeInput({
831
+ gvc,
832
+ title: '',
833
+ default: `${variantObj?.price ?? 0}`,
834
+ placeHolder: '',
835
+ callback: value => {
836
+ const n = parseInt(`${value ?? 0}`, 10);
837
+ if (variantObj && !isNaN(n) && n > 0) {
838
+ variantObj.price = n;
839
+ }
840
+ gvc.notifyDataChange(id);
841
+ },
842
+ });
843
+ },
844
+ divCreate: {
845
+ style: 'width: 120px;',
846
+ },
847
+ };
848
+ })()
849
+ )
850
+ ),
851
+ ]
852
+ .map(
853
+ (item, index) => html`
742
854
  <div class="d-flex align-items-center" style="${getIndexStyle(index)}">
743
855
  ${item}
744
856
  </div>
745
857
  `
746
- )
747
- .join('')}
858
+ )
859
+ .join('')}
748
860
  </div>`;
749
- })
750
- .join('')}
861
+ })
862
+ .join('')}
751
863
  </div>
752
864
  </div>`
753
- : ''}
865
+ : ''}
754
866
  `;
755
- } catch (error) {
756
- console.error(error);
757
- return '';
758
- }
759
- },
760
- onCreate: () => {
761
- if (priceVM.loading) {
762
- Promise.all([
763
- ApiUser.getUserGroupList('level').then(r => {
764
- if (r.result && r.response && Array.isArray(r.response.data)) {
765
- return r.response.data.map((item: any) => ({
766
- type: 'level',
767
- key: item.tag || 'default',
768
- name: item.title.replace('會員等級 - ', ''),
769
- }));
770
- }
771
- return [];
772
- }),
773
- ApiUser.getPublicConfig('store_manager', 'manager').then((r: any) => {
774
- if (r.result && Array.isArray(r.response.value.list)) {
775
- return r.response.value.list.map((d: { id: string; name: string }) => ({
776
- type: 'store',
777
- key: d.id,
778
- name: d.name,
779
- }));
780
- }
781
- return [];
782
- }),
783
- ApiUser.getPublicConfig('user_general_tags', 'manager').then((r: any) => {
784
- if (r.result && Array.isArray(r.response.value.list)) {
785
- return r.response.value.list.map((tag: string) => ({
786
- type: 'tags',
787
- key: tag,
788
- name: tag,
789
- }));
790
- }
791
- return [];
792
- }),
793
- ]).then((dlist: { type: MultiSaleType; key: string; name: string }[][]) => {
794
- priceVM.typeData = dlist.flat();
795
- priceVM.loading = false;
796
- gvc.notifyDataChange(priceVM.id);
797
- });
798
- } else {
799
- // 滾動監視事件
800
- const scrollDiv = document.getElementById('scrollDiv');
801
- if (scrollDiv) {
802
- function setStatus(scrollDiv: HTMLElement, delta: number = 360) {
803
- const status = scrollDiv.scrollLeft > delta;
804
- if (priceVM.showPriceDetail !== status) {
805
- priceVM.showPriceDetail = status;
806
- }
807
- }
867
+ } catch (error) {
868
+ console.error(error);
869
+ return '';
870
+ }
871
+ },
872
+ onCreate: () => {
873
+ if (priceVM.loading) {
874
+ Promise.all([
875
+ ApiUser.getUserGroupList('level').then(r => {
876
+ if (r.result && r.response && Array.isArray(r.response.data)) {
877
+ return r.response.data.map((item: any) => ({
878
+ type: 'level',
879
+ key: item.tag || 'default',
880
+ name: item.title.replace('會員等級 - ', ''),
881
+ }));
882
+ }
883
+ return [];
884
+ }),
885
+ ApiUser.getPublicConfig('store_manager', 'manager').then((r: any) => {
886
+ if (r.result && Array.isArray(r.response.value.list)) {
887
+ return r.response.value.list.map((d: { id: string; name: string }) => ({
888
+ type: 'store',
889
+ key: d.id,
890
+ name: d.name,
891
+ }));
892
+ }
893
+ return [];
894
+ }),
895
+ ApiUser.getPublicConfig('user_general_tags', 'manager').then((r: any) => {
896
+ if (r.result && Array.isArray(r.response.value.list)) {
897
+ return r.response.value.list.map((tag: string) => ({
898
+ type: 'tags',
899
+ key: tag,
900
+ name: tag,
901
+ }));
902
+ }
903
+ return [];
904
+ }),
905
+ ]).then((dlist: { type: MultiSaleType; key: string; name: string }[][]) => {
906
+ priceVM.typeData = dlist.flat();
907
+ priceVM.loading = false;
908
+ gvc.notifyDataChange(priceVM.id);
909
+ });
910
+ } else {
911
+ // 滾動監視事件
912
+ const scrollDiv = document.getElementById('scrollDiv');
913
+ if (scrollDiv) {
914
+ function setStatus(scrollDiv: HTMLElement, delta: number = 360) {
915
+ const status = scrollDiv.scrollLeft > delta;
916
+ if (priceVM.showPriceDetail !== status) {
917
+ priceVM.showPriceDetail = status;
918
+ }
919
+ }
808
920
 
809
- if (isDesktop) {
810
- scrollDiv.addEventListener('scroll', () => {
811
- setStatus(scrollDiv);
812
- });
813
- } else {
814
- scrollDiv.addEventListener('touchmove', () => {
815
- setStatus(scrollDiv);
816
- });
817
- }
818
- }
819
- }
820
- },
821
- });
822
- })()
823
- ),
824
- postMD.product_category === 'commodity'
825
- ? BgWidget.mainCard(
826
- obj.gvc.bindView(() => {
827
- let loading = true;
828
- let dataList: any = [];
829
- postMD.designated_logistics = postMD.designated_logistics ?? { group: '' };
921
+ if (isDesktop) {
922
+ scrollDiv.addEventListener('scroll', () => {
923
+ setStatus(scrollDiv);
924
+ });
925
+ } else {
926
+ scrollDiv.addEventListener('touchmove', () => {
927
+ setStatus(scrollDiv);
928
+ });
929
+ }
930
+ }
931
+ }
932
+ },
933
+ });
934
+ })()
935
+ )
936
+ }
937
+ function drawLogisticsSection() {
938
+ return postMD.product_category === 'commodity'
939
+ ? BgWidget.mainCard(
940
+ obj.gvc.bindView(() => {
941
+ let loading = true;
942
+ let dataList: any = [];
943
+ postMD.designated_logistics = postMD.designated_logistics ?? { group: '' };
830
944
 
831
- return {
832
- bind: 'designatedLogistics',
833
- view: () => {
834
- if (loading) {
835
- return '';
836
- }
837
- return html` <div class="tx_700">指定物流配送方式</div>
945
+ return {
946
+ bind: 'designatedLogistics',
947
+ view: () => {
948
+ if (loading) {
949
+ return '';
950
+ }
951
+ return html` <div class="tx_700">指定物流配送方式</div>
838
952
  ${BgWidget.mbContainer(18)}
839
953
  ${gvc.bindView(() => {
840
- const id = gvc.glitter.getUUID();
841
- return {
842
- bind: id,
843
- view: () => {
844
- return html`
954
+ const id = gvc.glitter.getUUID();
955
+ return {
956
+ bind: id,
957
+ view: () => {
958
+ return html`
845
959
  <div style="display: flex; flex-direction: column; gap: 8px;">
846
960
  ${BgWidget.selectFilter({
847
- gvc: gvc,
848
- callback: text => {
849
- postMD.designated_logistics.type = text;
850
- gvc.notifyDataChange(id);
851
- },
852
- default: postMD.designated_logistics.type,
853
- options: [
854
- {
855
- key: 'all',
856
- value: '全部',
857
- },
858
- {
859
- key: 'designated',
860
- value: '指定物流群組',
861
- },
862
- ].filter(item => {
863
- return !(item.key === 'designated' && dataList.length === 0);
864
- }),
865
- style: 'width: 100%;',
866
- })}
961
+ gvc: gvc,
962
+ callback: text => {
963
+ postMD.designated_logistics.type = text;
964
+ gvc.notifyDataChange(id);
965
+ },
966
+ default: postMD.designated_logistics.type,
967
+ options: [
968
+ {
969
+ key: 'all',
970
+ value: '全部',
971
+ },
972
+ {
973
+ key: 'designated',
974
+ value: '指定物流群組',
975
+ },
976
+ ].filter(item => {
977
+ return !(item.key === 'designated' && dataList.length === 0);
978
+ }),
979
+ style: 'width: 100%;',
980
+ })}
867
981
  <div>
868
982
  ${(() => {
869
- switch (postMD.designated_logistics.type) {
870
- case 'designated':
871
- return BgWidget.selectDropList({
872
- gvc: gvc,
873
- callback: (value: []) => {
874
- postMD.designated_logistics.group = value;
875
- gvc.notifyDataChange(id);
876
- },
877
- default: postMD.designated_logistics.group || [],
878
- options: dataList.map((data: any) => {
879
- return {
880
- key: data.key,
881
- value: data.name,
882
- };
883
- }),
884
- style: 'width: 100%;',
885
- });
886
- default:
887
- return '';
888
- }
889
- })()}
983
+ switch (postMD.designated_logistics.type) {
984
+ case 'designated':
985
+ return BgWidget.selectDropList({
986
+ gvc: gvc,
987
+ callback: (value: []) => {
988
+ postMD.designated_logistics.group = value;
989
+ gvc.notifyDataChange(id);
990
+ },
991
+ default: postMD.designated_logistics.group || [],
992
+ options: dataList.map((data: any) => {
993
+ return {
994
+ key: data.key,
995
+ value: data.name,
996
+ };
997
+ }),
998
+ style: 'width: 100%;',
999
+ });
1000
+ default:
1001
+ return '';
1002
+ }
1003
+ })()}
890
1004
  </div>
891
1005
  </div>
892
1006
  `;
893
- },
894
- };
895
- })}`;
896
- },
897
- onCreate: () => {
898
- if (loading) {
899
- ApiUser.getPublicConfig('logistics_group', 'manager').then(r => {
900
- dataList = (() => {
901
- try {
902
- return r.response.value;
903
- } catch (error) {
904
- return dataList;
905
- }
906
- })();
907
- loading = false;
908
- gvc.notifyDataChange('designatedLogistics');
909
- });
910
- }
911
- },
912
- };
913
- })
914
- )
915
- : '',
916
- BgWidget.mainCard(
917
- obj.gvc.bindView(() => {
918
- const id = gvc.glitter.getUUID();
919
- return {
920
- bind: id,
921
- view: () => {
922
- postMD.relative_product = postMD.relative_product ?? [];
1007
+ },
1008
+ };
1009
+ })}`;
1010
+ },
1011
+ onCreate: () => {
1012
+ if (loading) {
1013
+ ApiUser.getPublicConfig('logistics_group', 'manager').then(r => {
1014
+ dataList = (() => {
923
1015
  try {
924
- return html`
1016
+ return r.response.value;
1017
+ } catch (error) {
1018
+ return dataList;
1019
+ }
1020
+ })();
1021
+ loading = false;
1022
+ gvc.notifyDataChange('designatedLogistics');
1023
+ });
1024
+ }
1025
+ },
1026
+ };
1027
+ })
1028
+ )
1029
+ : ''
1030
+ }
1031
+ function drawRelateProductSection() {
1032
+ return BgWidget.mainCard(
1033
+ obj.gvc.bindView(() => {
1034
+ const id = gvc.glitter.getUUID();
1035
+ return {
1036
+ bind: id,
1037
+ view: () => {
1038
+ postMD.relative_product = postMD.relative_product ?? [];
1039
+ try {
1040
+ return html`
925
1041
  <div style="font-weight: 700;" class="mb-3 d-flex flex-column">
926
1042
  相關商品 ${BgWidget.grayNote('相關商品將會顯示於商品頁底部')}
927
1043
  </div>
@@ -933,171 +1049,191 @@ export class ShoppingSettingAdvance {
933
1049
  <div class="tx_normal">商品列表</div>
934
1050
  </div>
935
1051
  ${BgWidget.grayButton(
936
- '選擇商品',
937
- gvc.event(() => {
938
- BgProduct.productsDialog({
939
- gvc: gvc,
940
- default: postMD.relative_product,
941
- callback: async value => {
942
- postMD.relative_product = value;
943
- gvc.notifyDataChange(id);
944
- },
945
- filter: dd => dd.key !== postMD.id,
946
- });
947
- }),
948
- { textStyle: 'font-weight: 400;' }
949
- )}
1052
+ '選擇商品',
1053
+ gvc.event(() => {
1054
+ BgProduct.productsDialog({
1055
+ gvc: gvc,
1056
+ default: postMD.relative_product,
1057
+ callback: async value => {
1058
+ postMD.relative_product = value;
1059
+ gvc.notifyDataChange(id);
1060
+ },
1061
+ filter: dd => dd.key !== postMD.id,
1062
+ });
1063
+ }),
1064
+ { textStyle: 'font-weight: 400;' }
1065
+ )}
950
1066
  </div>
951
1067
  ${gvc.bindView(() => {
952
- const vm: {
953
- id: string;
954
- loading: boolean;
955
- data: OptionsItem[];
956
- } = {
957
- id: gvc.glitter.getUUID(),
958
- loading: true,
959
- data: [],
960
- };
961
- BgProduct.getProductOpts(postMD.relative_product).then(res => {
962
- vm.data = res;
963
- vm.loading = false;
964
- gvc.notifyDataChange(vm.id);
965
- });
966
- return {
967
- bind: vm.id,
968
- view: async () => {
969
- if (vm.loading) {
970
- return BgWidget.spinner();
971
- }
972
- return vm.data
973
- .map((opt: OptionsItem, index) => {
974
- return html` <div
1068
+ const vm: {
1069
+ id: string;
1070
+ loading: boolean;
1071
+ data: OptionsItem[];
1072
+ } = {
1073
+ id: gvc.glitter.getUUID(),
1074
+ loading: true,
1075
+ data: [],
1076
+ };
1077
+ BgProduct.getProductOpts(postMD.relative_product).then(res => {
1078
+ vm.data = res;
1079
+ vm.loading = false;
1080
+ gvc.notifyDataChange(vm.id);
1081
+ });
1082
+ return {
1083
+ bind: vm.id,
1084
+ view: async () => {
1085
+ if (vm.loading) {
1086
+ return BgWidget.spinner();
1087
+ }
1088
+ return vm.data
1089
+ .map((opt: OptionsItem, index) => {
1090
+ return html` <div
975
1091
  class="d-flex align-items-center form-check-label c_updown_label gap-3"
976
1092
  >
977
1093
  <span class="tx_normal">${index + 1} .</span>
978
1094
  ${BgWidget.validImageBox({
979
- gvc: gvc,
980
- image: opt.image,
981
- width: 40,
982
- })}
1095
+ gvc: gvc,
1096
+ image: opt.image,
1097
+ width: 40,
1098
+ })}
983
1099
  <div class="tx_normal ${opt.note ? 'mb-1' : ''}">${opt.value}</div>
984
1100
  ${opt.note ? html` <div class="tx_gray_12">${opt.note}</div> ` : ''}
985
1101
  </div>`;
986
- })
987
- .join('');
988
- },
989
- divCreate: {
990
- class: `d-flex py-2 flex-column`,
991
- style: `gap:10px;`,
992
- },
993
- };
994
- })}
995
- `;
996
- } catch (e) {
997
- console.error(e);
998
- return '';
999
- }
1102
+ })
1103
+ .join('');
1000
1104
  },
1001
1105
  divCreate: {
1002
- class: `w-100`,
1106
+ class: `d-flex py-2 flex-column`,
1107
+ style: `gap:10px;`,
1003
1108
  },
1004
1109
  };
1005
- })
1006
- ),
1007
- BgWidget.mainCard(
1008
- obj.gvc.bindView(() => {
1009
- const id = gvc.glitter.getUUID();
1010
- return {
1011
- bind: id,
1012
- view: () => {
1013
- postMD.relative_product = postMD.relative_product ?? [];
1014
- try {
1015
- return html`
1110
+ })}
1111
+ `;
1112
+ } catch (e) {
1113
+ console.error(e);
1114
+ return '';
1115
+ }
1116
+ },
1117
+ divCreate: {
1118
+ class: `w-100`,
1119
+ },
1120
+ };
1121
+ })
1122
+ )
1123
+ }
1124
+ function drawNoticeSection() {
1125
+ return BgWidget.mainCard(
1126
+ obj.gvc.bindView(() => {
1127
+ const id = gvc.glitter.getUUID();
1128
+ return {
1129
+ bind: id,
1130
+ view: () => {
1131
+ postMD.relative_product = postMD.relative_product ?? [];
1132
+ try {
1133
+ return html`
1016
1134
  <div style="font-weight: 700;" class="mb-3 d-flex flex-column">
1017
1135
  ${carTitle}通知 ${BgWidget.grayNote(`購買此${carTitle}會收到的通知信,內容為空則不寄送。`)}
1018
1136
  </div>
1019
1137
  ${BgWidget.richTextEditor({
1020
- gvc: gvc,
1021
- content: postMD.email_notice ?? '',
1022
- callback: content => {
1023
- postMD.email_notice = content;
1024
- },
1025
- title: '內容編輯',
1026
- quick_insert: BgWidget.richTextQuickList.filter(item => {
1027
- return !['訂單號碼', '訂單金額'].includes(item.title);
1028
- }),
1029
- })}
1138
+ gvc: gvc,
1139
+ content: postMD.email_notice ?? '',
1140
+ callback: content => {
1141
+ postMD.email_notice = content;
1142
+ },
1143
+ title: '內容編輯',
1144
+ quick_insert: BgWidget.richTextQuickList.filter(item => {
1145
+ return !['訂單號碼', '訂單金額'].includes(item.title);
1146
+ }),
1147
+ })}
1030
1148
  `;
1031
- } catch (e) {
1032
- console.error(e);
1033
- return '';
1034
- }
1035
- },
1036
- divCreate: {
1037
- class: `w-100`,
1038
- },
1039
- };
1040
- })
1041
- ),
1042
- BgWidget.mainCard(
1043
- obj.gvc.bindView(() => {
1044
- const id = obj.gvc.glitter.getUUID();
1045
- return {
1046
- bind: id,
1047
- view: () => {
1048
- return [
1049
- html` <div class="title-container px-0">
1149
+ } catch (e) {
1150
+ console.error(e);
1151
+ return '';
1152
+ }
1153
+ },
1154
+ divCreate: {
1155
+ class: `w-100`,
1156
+ },
1157
+ };
1158
+ })
1159
+ )
1160
+ }
1161
+ function drawAIDesSection() {
1162
+ return BgWidget.mainCard(
1163
+ obj.gvc.bindView(() => {
1164
+ const id = obj.gvc.glitter.getUUID();
1165
+ return {
1166
+ bind: id,
1167
+ view: () => {
1168
+ return [
1169
+ html` <div class="title-container px-0">
1050
1170
  <div style="color:#393939;font-weight: 700;">AI 選品</div>
1051
1171
  <div class="flex-fill"></div>
1052
1172
  ${BgWidget.grayButton(
1053
- '設定描述語句',
1173
+ '設定描述語句',
1174
+ gvc.event(() => {
1175
+ let description = postMD.ai_description;
1176
+ BgWidget.settingDialog({
1177
+ gvc: gvc,
1178
+ title: '描述語句',
1179
+ innerHTML: gvc => {
1180
+ return BgWidget.textArea({
1181
+ gvc: gvc,
1182
+ title: '',
1183
+ default: postMD.ai_description || '',
1184
+ placeHolder: `請告訴我這是什麼商品,範例:現代極簡風格的淺灰色布藝沙發,可以同時乘坐3個人,配金屬腳座,採用鈦合金製作十分的堅固。`,
1185
+ callback: text => {
1186
+ description = text;
1187
+ },
1188
+ style: `min-height:100px;`,
1189
+ });
1190
+ },
1191
+ footer_html: gvc => {
1192
+ return [
1193
+ BgWidget.save(
1054
1194
  gvc.event(() => {
1055
- let description = postMD.ai_description;
1056
- BgWidget.settingDialog({
1057
- gvc: gvc,
1058
- title: '描述語句',
1059
- innerHTML: gvc => {
1060
- return BgWidget.textArea({
1061
- gvc: gvc,
1062
- title: '',
1063
- default: postMD.ai_description || '',
1064
- placeHolder: `請告訴我這是什麼商品,範例:現代極簡風格的淺灰色布藝沙發,可以同時乘坐3個人,配金屬腳座,採用鈦合金製作十分的堅固。`,
1065
- callback: text => {
1066
- description = text;
1067
- },
1068
- style: `min-height:100px;`,
1069
- });
1070
- },
1071
- footer_html: gvc => {
1072
- return [
1073
- BgWidget.save(
1074
- gvc.event(() => {
1075
- postMD.ai_description = description;
1076
- gvc.notifyDataChange(id);
1077
- gvc.closeDialog();
1078
- })
1079
- ),
1080
- ].join('');
1081
- },
1082
- });
1083
- }),
1084
- {
1085
- textStyle: 'width:100%;',
1086
- }
1087
- )}
1195
+ postMD.ai_description = description;
1196
+ gvc.notifyDataChange(id);
1197
+ gvc.closeDialog();
1198
+ })
1199
+ ),
1200
+ ].join('');
1201
+ },
1202
+ });
1203
+ }),
1204
+ {
1205
+ textStyle: 'width:100%;',
1206
+ }
1207
+ )}
1088
1208
  </div>`,
1089
- html`
1209
+ html`
1090
1210
  <div>
1091
1211
  ${postMD.ai_description
1092
- ? `您設定的描述語句:${postMD.ai_description}`
1093
- : BgWidget.grayNote('尚未設定描述語句,透過設定描述語句,可以幫助AI更準確的定位產品。')}
1212
+ ? `您設定的描述語句:${postMD.ai_description}`
1213
+ : BgWidget.grayNote('尚未設定描述語句,透過設定描述語句,可以幫助AI更準確的定位產品。')}
1094
1214
  </div>
1095
1215
  `,
1096
- ].join(BgWidget.mbContainer(18));
1097
- },
1098
- };
1099
- })
1100
- ),
1216
+ ].join(BgWidget.mbContainer(18));
1217
+ },
1218
+ };
1219
+ })
1220
+ )
1221
+ }
1222
+ return BgWidget.container(
1223
+ gvc.bindView(() => {
1224
+ return {
1225
+ bind: id,
1226
+ view: () => {
1227
+ return [
1228
+ drawReverseSection(),
1229
+ drawTagSection(),
1230
+ drawBuyLimit(),
1231
+ drawTaxSection(),
1232
+ drawSpecialPriceSection(),
1233
+ drawLogisticsSection(),
1234
+ drawRelateProductSection(),
1235
+ drawNoticeSection(),
1236
+ drawAIDesSection(),
1101
1237
  ]
1102
1238
  .filter(Boolean)
1103
1239
  .join(html` <div class="my-3"></div>`);