ts-glitter 21.6.2 → 21.6.5

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 (53) hide show
  1. package/lowcode/Entry.js +1 -1
  2. package/lowcode/Entry.ts +1 -1
  3. package/lowcode/backend-manager/bg-blog.js +186 -8
  4. package/lowcode/backend-manager/bg-blog.ts +206 -11
  5. package/lowcode/backend-manager/bg-widget.js +20 -17
  6. package/lowcode/backend-manager/bg-widget.ts +23 -19
  7. package/lowcode/cms-plugin/permission-setting.js +2 -3
  8. package/lowcode/cms-plugin/permission-setting.ts +2 -3
  9. package/lowcode/cms-plugin/shopping-discount-setting.js +1711 -341
  10. package/lowcode/cms-plugin/shopping-discount-setting.ts +1938 -327
  11. package/lowcode/editor-components/global-widget/bridge.js +30 -0
  12. package/lowcode/editor-components/global-widget/bridge.ts +131 -2
  13. package/lowcode/form-view/e-commerce/product-select.js +6 -16
  14. package/lowcode/form-view/e-commerce/product-select.ts +16 -18
  15. package/lowcode/glitter-base/global/language.js +2 -0
  16. package/lowcode/glitter-base/global/language.ts +2 -0
  17. package/lowcode/glitterBundle/html-component/global-widget.js +0 -74
  18. package/lowcode/glitterBundle/html-component/global-widget.ts +1 -81
  19. package/lowcode/official_view_component/official/component.ts +0 -2
  20. package/lowcode/public-components/checkout/index.js +1 -1
  21. package/lowcode/public-components/checkout/index.ts +1 -1
  22. package/lowcode/public-components/user-manager/um-info.js +4 -0
  23. package/lowcode/public-components/user-manager/um-info.ts +4 -1
  24. package/lowcode/public-components/user-manager/um-login.js +3 -3
  25. package/lowcode/public-components/user-manager/um-login.ts +3 -6
  26. package/lowcode/public-components/user-manager/um-rebate.js +6 -2
  27. package/lowcode/public-components/user-manager/um-rebate.ts +28 -2
  28. package/package.json +1 -1
  29. package/src/api-public/controllers/shop.js +1 -0
  30. package/src/api-public/controllers/shop.js.map +1 -1
  31. package/src/api-public/controllers/shop.ts +1 -0
  32. package/src/api-public/services/data-analyze.d.ts +1 -1
  33. package/src/api-public/services/rebate.d.ts +14 -2
  34. package/src/api-public/services/rebate.js +66 -24
  35. package/src/api-public/services/rebate.js.map +1 -1
  36. package/src/api-public/services/rebate.ts +91 -24
  37. package/src/api-public/services/schedule.js +1 -3
  38. package/src/api-public/services/schedule.js.map +1 -1
  39. package/src/api-public/services/schedule.ts +1 -1
  40. package/src/api-public/services/shopping.js +35 -6
  41. package/src/api-public/services/shopping.js.map +1 -1
  42. package/src/api-public/services/shopping.ts +45 -12
  43. package/src/index.js +17 -7
  44. package/src/index.js.map +1 -1
  45. package/src/modules/database.js +1 -1
  46. package/src/modules/database.js.map +1 -1
  47. package/src/modules/database.ts +1 -1
  48. package/src/run.js +1 -1
  49. package/src/run.js.map +1 -1
  50. package/src/run.ts +1 -1
  51. package/src/services/system-schedule.js +1 -1
  52. package/src/services/system-schedule.js.map +1 -1
  53. package/src/services/system-schedule.ts +3 -2
@@ -227,14 +227,14 @@ export class ShoppingDiscountSetting {
227
227
  ${BgWidget.mbContainer(120)}
228
228
  `);
229
229
  } else if (vm.type == 'replace') {
230
- return this.voucherEditor({
230
+ return this.voucherEditorV2({
231
231
  vm: vm,
232
232
  gvc: gvc,
233
233
  type: 'replace',
234
234
  reBackType: voucher_type,
235
235
  });
236
236
  } else {
237
- return this.voucherEditor({
237
+ return this.voucherEditorV2({
238
238
  vm: vm,
239
239
  gvc: gvc,
240
240
  type: 'add',
@@ -265,6 +265,105 @@ export class ShoppingDiscountSetting {
265
265
  { title: '特定商品', value: 'product' },
266
266
  ];
267
267
 
268
+ static emptyVoucher = (reBackType: RebackType) => {
269
+ return {
270
+ title: '',
271
+ code: '',
272
+ trigger: 'auto',
273
+ method: 'fixed',
274
+ value: '0',
275
+ for: 'all',
276
+ forKey: [],
277
+ device: ['normal'],
278
+ rule: 'min_price',
279
+ ruleValue: 1000,
280
+ startDate: this.getDateTime().date,
281
+ startTime: this.getDateTime().time,
282
+ endDate: undefined,
283
+ endTime: undefined,
284
+ status: 1,
285
+ type: 'voucher',
286
+ overlay: false,
287
+ start_ISO_Date: '',
288
+ end_ISO_Date: '',
289
+ reBackType: reBackType,
290
+ rebateEndDay: '30',
291
+ target: 'all',
292
+ targetList: [],
293
+ macroLimited: 0,
294
+ microLimited: 0,
295
+ counting: 'single',
296
+ conditionType: 'order',
297
+ includeDiscount: 'before',
298
+ productOffStart: 'price_desc',
299
+ selectShipment: { type: 'all', list: [] },
300
+ };
301
+ };
302
+
303
+ static summaryTextList(voucherData: VoucherData) {
304
+ return [
305
+ `活動標題:${voucherData.title && voucherData.title.length > 0 ? voucherData.title : '尚無標題'}`,
306
+ `適用商品:${(() => {
307
+ const forData = ShoppingDiscountSetting.productForList.find(item => item.value === voucherData.for);
308
+ return forData ? forData.title : '';
309
+ })()}`,
310
+ `活動方式:${(() => {
311
+ if (voucherData.trigger === 'auto') return '自動折扣';
312
+ if (voucherData.trigger === 'distribution') return '分銷連結';
313
+ if (voucherData.trigger === 'code') return `優惠代碼「${voucherData.code ?? ''}」`;
314
+ return '';
315
+ })()}`,
316
+ `活動對象:${(() => {
317
+ const targetMapping: Record<string, string> = {
318
+ customer: '特定顧客',
319
+ levels: '會員等級',
320
+ group: '顧客分群',
321
+ all: '所有顧客',
322
+ };
323
+
324
+ return targetMapping[voucherData.target] || targetMapping.all;
325
+ })()}`,
326
+ '',
327
+ `消費條件:${(() => {
328
+ if (voucherData.rule === 'min_price') return '最少消費金額';
329
+ if (voucherData.rule === 'min_count') return '最少購買數量';
330
+ return '';
331
+ })()}`,
332
+ `條件值:${(() => {
333
+ if (voucherData.rule === 'min_price') return voucherData.ruleValue + ' 元';
334
+ if (voucherData.rule === 'min_count') return voucherData.ruleValue + ' 個';
335
+ return '';
336
+ })()}`,
337
+ `折扣優惠:${(() => {
338
+ const voucherMessages: { [key: string]: (method: string, value: string) => string } = {
339
+ rebate: (method, value) =>
340
+ method === 'fixed' ? `${value} 點購物金` : `符合條件商品總額的 ${value} %作為購物金`,
341
+ discount: (method, value) => (method === 'fixed' ? `折扣 ${value} 元` : `符合條件商品折扣 ${value} %`),
342
+ shipment_free: () => '免運費',
343
+ };
344
+
345
+ const messageFunction = voucherMessages[voucherData.reBackType];
346
+ return messageFunction ? messageFunction(voucherData.method, voucherData.value) : '';
347
+ })()}`,
348
+ `將此優惠套用至:${(() => {
349
+ const length = voucherData.forKey?.length ?? 0;
350
+ const forMaps: Record<string, string> = {
351
+ collection: `指定 ${length} 種商品分類`,
352
+ product: `指定 ${length} 個商品`,
353
+ all: '所有商品',
354
+ };
355
+ return forMaps[voucherData.for] || forMaps.all;
356
+ })()}`,
357
+ '',
358
+ voucherData.overlay ? '可以疊加,套用最大優惠' : '不可疊加',
359
+ `啟用時間:${voucherData.startDate ?? '未設定日期'} ${voucherData.startTime ?? '尚未設定時間'}`,
360
+ `結束時間:${(() => {
361
+ if (!voucherData.endDate) return '無期限';
362
+ return `${voucherData.endDate ?? '未設定日期'} ${voucherData.endTime ?? '尚未設定時間'}`;
363
+ })()}`,
364
+ ];
365
+ }
366
+
268
367
  public static voucherEditor(obj: { vm: any; gvc: GVC; type?: 'add' | 'replace'; defData?: any; reBackType: string }) {
269
368
  const gvc = obj.gvc;
270
369
  const glitter = gvc.glitter;
@@ -954,338 +1053,348 @@ export class ShoppingDiscountSetting {
954
1053
  .map(str => html`<div>${str}</div>`)
955
1054
  .join(BgWidget.horizontalLine())
956
1055
  ),
957
- ...(['shipment_free', 'add_on_items', 'giveaway'].includes(voucherData.reBackType)) ? []:[
958
- BgWidget.mainCard(
959
- gvc.bindView(() => {
960
- const id = glitter.getUUID();
961
- const originForType = String(voucherData.for);
962
- voucherData.forKey = voucherData.forKey ?? [];
963
- let defKeys: any = {
964
- collection: JSON.parse(JSON.stringify(voucherData.forKey)),
965
- product: JSON.parse(JSON.stringify(voucherData.forKey)),
966
- manager_tag: JSON.parse(JSON.stringify(voucherData.forKey)),
967
- };
968
-
969
- return {
970
- bind: id,
971
- dataList: [
972
- { obj: voucherData, key: 'method' },
973
- { obj: voucherData, key: 'reBackType' },
974
- ],
975
- view: () => {
976
- return [
977
- ...(() => {
978
- if (['shipment_free', 'add_on_items', 'giveaway'].includes(voucherData.reBackType)) {
979
- return [];
980
- }
981
-
982
- const valueInput = (obj: { startText?: string; endText?: string }) => {
983
- return BgWidget.editeInput({
984
- gvc: gvc,
985
- type: 'number',
986
- divStyle: 'width:150px;',
987
- title: '',
988
- default: voucherData.value,
989
- placeHolder: '',
990
- callback: text => {
991
- const texInt = parseInt(text, 10);
992
- if (voucherData.method === 'percent' && (texInt > 100 || texInt < 0)) {
993
- dialog.infoMessage({ text: '數值需介於0~100' });
994
- gvc.notifyDataChange(id);
995
- gvc.notifyDataChange(pageVM.countingID);
996
- } else {
997
- voucherData.value = text;
998
- }
999
- },
1000
- startText: obj.startText,
1001
- endText: obj.endText,
1002
- });
1003
- };
1056
+ ...(['shipment_free', 'add_on_items', 'giveaway'].includes(voucherData.reBackType)
1057
+ ? []
1058
+ : [
1059
+ BgWidget.mainCard(
1060
+ gvc.bindView(() => {
1061
+ const id = glitter.getUUID();
1062
+ const originForType = String(voucherData.for);
1063
+ voucherData.forKey = voucherData.forKey ?? [];
1064
+ let defKeys: any = {
1065
+ collection: JSON.parse(JSON.stringify(voucherData.forKey)),
1066
+ product: JSON.parse(JSON.stringify(voucherData.forKey)),
1067
+ manager_tag: JSON.parse(JSON.stringify(voucherData.forKey)),
1068
+ };
1004
1069
 
1070
+ return {
1071
+ bind: id,
1072
+ dataList: [
1073
+ { obj: voucherData, key: 'method' },
1074
+ { obj: voucherData, key: 'reBackType' },
1075
+ ],
1076
+ view: () => {
1005
1077
  return [
1006
- html` <div>
1007
- <div class="tx_700">折扣金額</div>
1008
- ${BgWidget.mbContainer(18)}
1009
- ${BgWidget.multiCheckboxContainer(
1010
- gvc,
1011
- [
1012
- {
1013
- key: 'fixed',
1014
- name: '固定金額',
1015
- innerHtml: valueInput({ startText: '$' }),
1016
- },
1017
- {
1018
- key: 'percent',
1019
- name: '百分比',
1020
- innerHtml: valueInput({ endText: '%' }),
1021
- },
1022
- ],
1023
- [voucherData.method],
1024
- text => {
1025
- voucherData.value = '0';
1026
- voucherData.method = text[0] as Method;
1027
- gvc.notifyDataChange(pageVM.conditionID);
1028
- },
1029
- { single: true }
1030
- )}
1031
- </div>`,
1032
- ];
1033
- })(),
1034
- ...(() => {
1035
- if (voucherData.trigger === 'distribution') {
1036
- return [];
1037
- }
1078
+ ...(() => {
1079
+ if (
1080
+ ['shipment_free', 'add_on_items', 'giveaway'].includes(voucherData.reBackType)
1081
+ ) {
1082
+ return [];
1083
+ }
1038
1084
 
1039
- return [
1040
- html`
1041
- <div class="tx_700">套用至</div>
1042
- ${BgWidget.mbContainer(18)}
1043
- ${EditorElem.radio({
1044
- gvc: gvc,
1045
- title: '',
1046
- def: voucherData.for ?? 'all',
1047
- array: ShoppingDiscountSetting.productForList,
1048
- callback: text => {
1049
- voucherData.forKey = defKeys[text];
1050
- voucherData.for = text as VoucherForType;
1051
- gvc.notifyDataChange(id);
1052
- },
1053
- oneLine: true,
1054
- })}
1055
- ${BgWidget.mbContainer(8)}
1056
- ${(() => {
1057
- switch (voucherData.for) {
1058
- case 'manager_tag':
1059
- return gvc.bindView(() => {
1060
- const subVM = {
1061
- id: gvc.glitter.getUUID(),
1062
- dataList: originForType === 'manager_tag' ? [...defKeys.manager_tag] : [],
1063
- };
1085
+ const valueInput = (obj: { startText?: string; endText?: string }) => {
1086
+ return BgWidget.editeInput({
1087
+ gvc: gvc,
1088
+ type: 'number',
1089
+ divStyle: 'width:150px;',
1090
+ title: '',
1091
+ default: voucherData.value,
1092
+ placeHolder: '',
1093
+ callback: text => {
1094
+ const texInt = parseInt(text, 10);
1095
+ if (voucherData.method === 'percent' && (texInt > 100 || texInt < 0)) {
1096
+ dialog.infoMessage({ text: '數值需介於0~100' });
1097
+ gvc.notifyDataChange(id);
1098
+ gvc.notifyDataChange(pageVM.countingID);
1099
+ } else {
1100
+ voucherData.value = text;
1101
+ }
1102
+ },
1103
+ startText: obj.startText,
1104
+ endText: obj.endText,
1105
+ });
1106
+ };
1064
1107
 
1065
- return {
1066
- bind: subVM.id,
1067
- view: () => {
1068
- return html`
1069
- <div class="d-flex flex-column p-2" style="gap: 18px;">
1070
- <div
1071
- class="d-flex align-items-center gray-bottom-line-18"
1072
- style="gap: 24px; justify-content: space-between;"
1073
- >
1074
- <div class="form-check-label c_updown_label">
1075
- <div class="tx_normal">標籤列表</div>
1076
- </div>
1077
- ${BgWidget.grayButton(
1078
- '選擇標籤',
1079
- gvc.event(() => {
1080
- BgProduct.useProductTags({
1081
- gvc,
1082
- config_key: 'product_manager_tags',
1083
- def:
1084
- originForType === 'manager_tag' && voucherData.forKey
1085
- ? voucherData.forKey.map(item => `${item}`)
1086
- : [],
1087
- callback: async value => {
1088
- voucherData.forKey = value;
1089
- defKeys.manager_tag = value;
1090
- subVM.dataList = value;
1091
- gvc.notifyDataChange(subVM.id);
1092
- },
1093
- });
1094
- }),
1095
- { textStyle: 'font-weight: 400;' }
1096
- )}
1097
- </div>
1098
- ${obj.gvc.map(
1099
- subVM.dataList.map((opt, index) => {
1100
- return html` <div
1101
- class="d-flex align-items-center form-check-label c_updown_label gap-3"
1102
- >
1103
- <span class="tx_normal">${index + 1}. #${opt}</span>
1104
- </div>`;
1105
- })
1106
- )}
1107
- </div>
1108
- `;
1109
- },
1110
- };
1111
- });
1112
- case 'collection':
1113
- return gvc.bindView(() => {
1114
- const subVM = {
1115
- id: gvc.glitter.getUUID(),
1116
- loading: true,
1117
- dataList: [] as OptionsItem[],
1118
- };
1119
- return {
1120
- bind: subVM.id,
1121
- view: () => {
1122
- if (subVM.loading) {
1123
- return BgWidget.spinner();
1124
- }
1125
- return html`
1126
- <div class="d-flex flex-column p-2" style="gap: 18px;">
1127
- <div
1128
- class="d-flex align-items-center gray-bottom-line-18"
1129
- style="gap: 24px; justify-content: space-between;"
1130
- >
1131
- <div class="form-check-label c_updown_label">
1132
- <div class="tx_normal">分類列表</div>
1133
- </div>
1134
- ${BgWidget.grayButton(
1135
- '選擇分類',
1136
- gvc.event(() => {
1137
- BgProduct.collectionsDialog({
1138
- gvc: gvc,
1139
- default: voucherData.forKey ?? [],
1140
- callback: async value => {
1141
- voucherData.forKey = value;
1142
- defKeys.collection = value;
1143
- subVM.dataList = await BgProduct.getCollectiosOpts(value);
1144
- subVM.loading = true;
1145
- gvc.notifyDataChange(subVM.id);
1146
- },
1147
- });
1148
- }),
1149
- { textStyle: 'font-weight: 400;' }
1150
- )}
1151
- </div>
1152
- ${obj.gvc.map(
1153
- subVM.dataList.map((opt: OptionsItem, index) => {
1154
- return html` <div
1155
- class="d-flex align-items-center form-check-label c_updown_label gap-3"
1156
- >
1157
- <span class="tx_normal">${index + 1}. ${opt.value}</span>
1158
- ${opt.note
1159
- ? html` <span class="tx_gray_12 ms-2">${opt.note}</span> `
1160
- : ''}
1161
- </div>`;
1162
- })
1163
- )}
1164
- </div>
1165
- `;
1108
+ return [
1109
+ html` <div>
1110
+ <div class="tx_700">折扣金額</div>
1111
+ ${BgWidget.mbContainer(18)}
1112
+ ${BgWidget.multiCheckboxContainer(
1113
+ gvc,
1114
+ [
1115
+ {
1116
+ key: 'fixed',
1117
+ name: '固定金額',
1118
+ innerHtml: valueInput({ startText: '$' }),
1166
1119
  },
1167
- onCreate: () => {
1168
- if (subVM.loading) {
1169
- if (voucherData.forKey.length === 0) {
1170
- setTimeout(() => {
1171
- subVM.dataList = [];
1172
- subVM.loading = false;
1173
- gvc.notifyDataChange(subVM.id);
1174
- }, 200);
1175
- } else {
1176
- new Promise<OptionsItem[]>(resolve => {
1177
- resolve(BgProduct.getCollectiosOpts(voucherData.forKey));
1178
- }).then(data => {
1179
- subVM.dataList = data;
1180
- subVM.loading = false;
1181
- gvc.notifyDataChange(subVM.id);
1182
- });
1183
- }
1184
- }
1120
+ {
1121
+ key: 'percent',
1122
+ name: '百分比',
1123
+ innerHtml: valueInput({ endText: '%' }),
1185
1124
  },
1186
- };
1187
- });
1188
- case 'product':
1189
- return gvc.bindView(() => {
1190
- const subVM = {
1191
- id: gvc.glitter.getUUID(),
1192
- loading: true,
1193
- dataList: [] as OptionsItem[],
1194
- };
1195
- return {
1196
- bind: subVM.id,
1197
- view: () => {
1198
- if (subVM.loading) {
1199
- return BgWidget.spinner();
1200
- }
1201
- return html`
1202
- <div class="d-flex flex-column p-2" style="gap: 18px;">
1203
- <div
1204
- class="d-flex align-items-center gray-bottom-line-18"
1205
- style="gap: 24px; justify-content: space-between;"
1206
- >
1207
- <div class="form-check-label c_updown_label">
1208
- <div class="tx_normal">商品列表</div>
1209
- </div>
1210
- ${BgWidget.grayButton(
1211
- '選擇商品',
1212
- gvc.event(() => {
1213
- BgProduct.productsDialog({
1214
- gvc: gvc,
1215
- default: voucherData.forKey ?? [],
1216
- callback: async value => {
1217
- voucherData.forKey = value;
1218
- defKeys.product = value;
1219
- subVM.dataList = await BgProduct.getProductOpts(
1220
- voucherData.forKey
1221
- );
1222
- subVM.loading = true;
1223
- gvc.notifyDataChange(subVM.id);
1224
- },
1225
- });
1226
- }),
1227
- { textStyle: 'font-weight: 400;' }
1228
- )}
1229
- </div>
1230
- ${subVM.dataList
1231
- .map((opt: OptionsItem, index) => {
1232
- return html` <div
1233
- class="d-flex align-items-center form-check-label c_updown_label gap-3"
1234
- >
1235
- <span class="tx_normal">${index + 1}.</span>
1236
- ${BgWidget.validImageBox({
1237
- gvc: gvc,
1238
- image: opt.image,
1239
- width: 40,
1240
- })}
1241
- <div class="tx_normal ${opt.note ? 'mb-1' : ''}">
1242
- ${opt.value}
1243
- </div>
1244
- ${opt.note
1245
- ? html` <div class="tx_gray_12">${opt.note}</div> `
1246
- : ''}
1247
- </div>`;
1248
- })
1249
- .join(``)}
1250
- </div>
1251
- `;
1252
- },
1253
- onCreate: () => {
1254
- if (subVM.loading) {
1255
- if (voucherData.forKey.length === 0) {
1256
- setTimeout(() => {
1257
- subVM.dataList = [];
1258
- subVM.loading = false;
1259
- gvc.notifyDataChange(subVM.id);
1260
- }, 200);
1261
- } else {
1262
- new Promise<OptionsItem[]>(resolve => {
1263
- resolve(BgProduct.getProductOpts(voucherData.forKey));
1264
- }).then(data => {
1265
- subVM.dataList = data;
1266
- subVM.loading = false;
1267
- gvc.notifyDataChange(subVM.id);
1268
- });
1269
- }
1270
- }
1271
- },
1272
- };
1273
- });
1274
- case 'all':
1275
- default:
1276
- return '';
1125
+ ],
1126
+ [voucherData.method],
1127
+ text => {
1128
+ voucherData.value = '0';
1129
+ voucherData.method = text[0] as Method;
1130
+ gvc.notifyDataChange(pageVM.conditionID);
1131
+ },
1132
+ { single: true }
1133
+ )}
1134
+ </div>`,
1135
+ ];
1136
+ })(),
1137
+ ...(() => {
1138
+ if (voucherData.trigger === 'distribution') {
1139
+ return [];
1277
1140
  }
1278
- })()}
1279
- `,
1280
- ];
1281
- })(),
1282
- ].join(BgWidget.horizontalLine());
1283
- },
1284
- };
1285
- })
1286
- )
1287
- ]
1288
- ,
1141
+
1142
+ return [
1143
+ html`
1144
+ <div class="tx_700">套用至</div>
1145
+ ${BgWidget.mbContainer(18)}
1146
+ ${EditorElem.radio({
1147
+ gvc: gvc,
1148
+ title: '',
1149
+ def: voucherData.for ?? 'all',
1150
+ array: ShoppingDiscountSetting.productForList,
1151
+ callback: text => {
1152
+ voucherData.forKey = defKeys[text];
1153
+ voucherData.for = text as VoucherForType;
1154
+ gvc.notifyDataChange(id);
1155
+ },
1156
+ oneLine: true,
1157
+ })}
1158
+ ${BgWidget.mbContainer(8)}
1159
+ ${(() => {
1160
+ switch (voucherData.for) {
1161
+ case 'manager_tag':
1162
+ return gvc.bindView(() => {
1163
+ const subVM = {
1164
+ id: gvc.glitter.getUUID(),
1165
+ dataList:
1166
+ originForType === 'manager_tag' ? [...defKeys.manager_tag] : [],
1167
+ };
1168
+
1169
+ return {
1170
+ bind: subVM.id,
1171
+ view: () => {
1172
+ return html`
1173
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
1174
+ <div
1175
+ class="d-flex align-items-center gray-bottom-line-18"
1176
+ style="gap: 24px; justify-content: space-between;"
1177
+ >
1178
+ <div class="form-check-label c_updown_label">
1179
+ <div class="tx_normal">標籤列表</div>
1180
+ </div>
1181
+ ${BgWidget.grayButton(
1182
+ '選擇標籤',
1183
+ gvc.event(() => {
1184
+ BgProduct.useProductTags({
1185
+ gvc,
1186
+ config_key: 'product_manager_tags',
1187
+ def:
1188
+ originForType === 'manager_tag' &&
1189
+ voucherData.forKey
1190
+ ? voucherData.forKey.map(item => `${item}`)
1191
+ : [],
1192
+ callback: async value => {
1193
+ voucherData.forKey = value;
1194
+ defKeys.manager_tag = value;
1195
+ subVM.dataList = value;
1196
+ gvc.notifyDataChange(subVM.id);
1197
+ },
1198
+ });
1199
+ }),
1200
+ { textStyle: 'font-weight: 400;' }
1201
+ )}
1202
+ </div>
1203
+ ${obj.gvc.map(
1204
+ subVM.dataList.map((opt, index) => {
1205
+ return html` <div
1206
+ class="d-flex align-items-center form-check-label c_updown_label gap-3"
1207
+ >
1208
+ <span class="tx_normal">${index + 1}. #${opt}</span>
1209
+ </div>`;
1210
+ })
1211
+ )}
1212
+ </div>
1213
+ `;
1214
+ },
1215
+ };
1216
+ });
1217
+ case 'collection':
1218
+ return gvc.bindView(() => {
1219
+ const subVM = {
1220
+ id: gvc.glitter.getUUID(),
1221
+ loading: true,
1222
+ dataList: [] as OptionsItem[],
1223
+ };
1224
+ return {
1225
+ bind: subVM.id,
1226
+ view: () => {
1227
+ if (subVM.loading) {
1228
+ return BgWidget.spinner();
1229
+ }
1230
+ return html`
1231
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
1232
+ <div
1233
+ class="d-flex align-items-center gray-bottom-line-18"
1234
+ style="gap: 24px; justify-content: space-between;"
1235
+ >
1236
+ <div class="form-check-label c_updown_label">
1237
+ <div class="tx_normal">分類列表</div>
1238
+ </div>
1239
+ ${BgWidget.grayButton(
1240
+ '選擇分類',
1241
+ gvc.event(() => {
1242
+ BgProduct.collectionsDialog({
1243
+ gvc: gvc,
1244
+ default: voucherData.forKey ?? [],
1245
+ callback: async value => {
1246
+ voucherData.forKey = value;
1247
+ defKeys.collection = value;
1248
+ subVM.dataList =
1249
+ await BgProduct.getCollectiosOpts(value);
1250
+ subVM.loading = true;
1251
+ gvc.notifyDataChange(subVM.id);
1252
+ },
1253
+ });
1254
+ }),
1255
+ { textStyle: 'font-weight: 400;' }
1256
+ )}
1257
+ </div>
1258
+ ${obj.gvc.map(
1259
+ subVM.dataList.map((opt: OptionsItem, index) => {
1260
+ return html` <div
1261
+ class="d-flex align-items-center form-check-label c_updown_label gap-3"
1262
+ >
1263
+ <span class="tx_normal"
1264
+ >${index + 1}. ${opt.value}</span
1265
+ >
1266
+ ${opt.note
1267
+ ? html`
1268
+ <span class="tx_gray_12 ms-2">${opt.note}</span>
1269
+ `
1270
+ : ''}
1271
+ </div>`;
1272
+ })
1273
+ )}
1274
+ </div>
1275
+ `;
1276
+ },
1277
+ onCreate: () => {
1278
+ if (subVM.loading) {
1279
+ if (voucherData.forKey.length === 0) {
1280
+ setTimeout(() => {
1281
+ subVM.dataList = [];
1282
+ subVM.loading = false;
1283
+ gvc.notifyDataChange(subVM.id);
1284
+ }, 200);
1285
+ } else {
1286
+ new Promise<OptionsItem[]>(resolve => {
1287
+ resolve(BgProduct.getCollectiosOpts(voucherData.forKey));
1288
+ }).then(data => {
1289
+ subVM.dataList = data;
1290
+ subVM.loading = false;
1291
+ gvc.notifyDataChange(subVM.id);
1292
+ });
1293
+ }
1294
+ }
1295
+ },
1296
+ };
1297
+ });
1298
+ case 'product':
1299
+ return gvc.bindView(() => {
1300
+ const subVM = {
1301
+ id: gvc.glitter.getUUID(),
1302
+ loading: true,
1303
+ dataList: [] as OptionsItem[],
1304
+ };
1305
+ return {
1306
+ bind: subVM.id,
1307
+ view: () => {
1308
+ if (subVM.loading) {
1309
+ return BgWidget.spinner();
1310
+ }
1311
+ return html`
1312
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
1313
+ <div
1314
+ class="d-flex align-items-center gray-bottom-line-18"
1315
+ style="gap: 24px; justify-content: space-between;"
1316
+ >
1317
+ <div class="form-check-label c_updown_label">
1318
+ <div class="tx_normal">商品列表</div>
1319
+ </div>
1320
+ ${BgWidget.grayButton(
1321
+ '選擇商品',
1322
+ gvc.event(() => {
1323
+ BgProduct.productsDialog({
1324
+ gvc: gvc,
1325
+ default: voucherData.forKey ?? [],
1326
+ callback: async value => {
1327
+ voucherData.forKey = value;
1328
+ defKeys.product = value;
1329
+ subVM.dataList = await BgProduct.getProductOpts(
1330
+ voucherData.forKey
1331
+ );
1332
+ subVM.loading = true;
1333
+ gvc.notifyDataChange(subVM.id);
1334
+ },
1335
+ });
1336
+ }),
1337
+ { textStyle: 'font-weight: 400;' }
1338
+ )}
1339
+ </div>
1340
+ ${subVM.dataList
1341
+ .map((opt: OptionsItem, index) => {
1342
+ return html` <div
1343
+ class="d-flex align-items-center form-check-label c_updown_label gap-3"
1344
+ >
1345
+ <span class="tx_normal">${index + 1}.</span>
1346
+ ${BgWidget.validImageBox({
1347
+ gvc: gvc,
1348
+ image: opt.image,
1349
+ width: 40,
1350
+ })}
1351
+ <div class="tx_normal ${opt.note ? 'mb-1' : ''}">
1352
+ ${opt.value}
1353
+ </div>
1354
+ ${opt.note
1355
+ ? html` <div class="tx_gray_12">${opt.note}</div> `
1356
+ : ''}
1357
+ </div>`;
1358
+ })
1359
+ .join(``)}
1360
+ </div>
1361
+ `;
1362
+ },
1363
+ onCreate: () => {
1364
+ if (subVM.loading) {
1365
+ if (voucherData.forKey.length === 0) {
1366
+ setTimeout(() => {
1367
+ subVM.dataList = [];
1368
+ subVM.loading = false;
1369
+ gvc.notifyDataChange(subVM.id);
1370
+ }, 200);
1371
+ } else {
1372
+ new Promise<OptionsItem[]>(resolve => {
1373
+ resolve(BgProduct.getProductOpts(voucherData.forKey));
1374
+ }).then(data => {
1375
+ subVM.dataList = data;
1376
+ subVM.loading = false;
1377
+ gvc.notifyDataChange(subVM.id);
1378
+ });
1379
+ }
1380
+ }
1381
+ },
1382
+ };
1383
+ });
1384
+ case 'all':
1385
+ default:
1386
+ return '';
1387
+ }
1388
+ })()}
1389
+ `,
1390
+ ];
1391
+ })(),
1392
+ ].join(BgWidget.horizontalLine());
1393
+ },
1394
+ };
1395
+ })
1396
+ ),
1397
+ ]),
1289
1398
  ['giveaway', 'add_on_items'].includes(voucherData.reBackType)
1290
1399
  ? BgWidget.mainCard(rebackProduct())
1291
1400
  : '',
@@ -1919,6 +2028,1508 @@ export class ShoppingDiscountSetting {
1919
2028
  };
1920
2029
  });
1921
2030
  }
2031
+
2032
+ public static voucherEditorV2(obj: {
2033
+ vm: any;
2034
+ gvc: GVC;
2035
+ type?: 'add' | 'replace';
2036
+ defData?: any;
2037
+ reBackType: RebackType;
2038
+ }) {
2039
+ const gvc = obj.gvc;
2040
+ const vm = obj.vm;
2041
+ const glitter = gvc.glitter;
2042
+ const dialog = new ShareDialog(glitter);
2043
+ const getUUID = glitter.getUUID;
2044
+
2045
+ const pageVM = {
2046
+ viewID: getUUID(),
2047
+ };
2048
+
2049
+ const voucherData: VoucherData = {
2050
+ ...this.emptyVoucher(obj.reBackType),
2051
+ ...vm.data,
2052
+ };
2053
+
2054
+ const cloneForKey = <T>(key: T): T => JSON.parse(JSON.stringify(key));
2055
+
2056
+ const setTitle = (title: string) => html`<div class="tx_700">${title}</div>`;
2057
+
2058
+ const voucherSettingCard = (array: string[][]) =>
2059
+ BgWidget.mainCard(
2060
+ array
2061
+ .map(stringArray => stringArray.filter(Boolean))
2062
+ .filter(stringArray => stringArray.length > 0)
2063
+ .map(stringArray => stringArray.join(BgWidget.mbContainer(18)))
2064
+ .join(BgWidget.horizontalLine({ margin: '18px 0' }))
2065
+ );
2066
+
2067
+ return gvc.bindView({
2068
+ bind: pageVM.viewID,
2069
+ view: () => {
2070
+ const ruleValue = voucherData.ruleValue;
2071
+ const isPercentMethod = voucherData.method === 'percent';
2072
+ const isShipmentFree = voucherData.reBackType === 'shipment_free';
2073
+ const floor = Math.floor(ruleValue / 2);
2074
+
2075
+ // 設定 counting
2076
+ if (isPercentMethod || isShipmentFree) {
2077
+ voucherData.counting = 'single';
2078
+ }
2079
+
2080
+ // 設定 conditionType
2081
+ if (isShipmentFree) {
2082
+ voucherData.conditionType = 'order';
2083
+ }
2084
+
2085
+ // 設定 forKey
2086
+ if (!voucherData.forKey) {
2087
+ voucherData.forKey = [];
2088
+ }
2089
+
2090
+ // 設定 add_on_products
2091
+ if (!Array.isArray(voucherData.add_on_products)) {
2092
+ voucherData.add_on_products = [];
2093
+ }
2094
+
2095
+ // 建立預設 keys 結構(型別安全)
2096
+ const defKeys: Record<VoucherForType, (string | number)[]> = {
2097
+ all: [],
2098
+ collection: cloneForKey(voucherData.forKey),
2099
+ product: cloneForKey(voucherData.forKey),
2100
+ manager_tag: cloneForKey(voucherData.forKey),
2101
+ };
2102
+
2103
+ // 活動啟用
2104
+ function status() {
2105
+ return html`<div class="d-flex gap-1">
2106
+ <div class="tx_normal">活動啟用</div>
2107
+ ${BgWidget.switchTextButton(gvc, voucherData.status === 1, {}, bool => {
2108
+ voucherData.status = bool ? 1 : 0;
2109
+ })}
2110
+ </div>`;
2111
+ }
2112
+
2113
+ // 活動標題
2114
+ function title() {
2115
+ return html` <div class="tx_normal">活動標題 ${BgWidget.requiredStar()}</div>
2116
+ ${BgWidget.mbContainer(8)}
2117
+ ${BgWidget.editeInput({
2118
+ gvc: gvc,
2119
+ title: '',
2120
+ default: voucherData.title,
2121
+ placeHolder: '請輸入活動標題',
2122
+ callback: text => {
2123
+ voucherData.title = text;
2124
+ },
2125
+ })}`;
2126
+ }
2127
+
2128
+ // 折扣方式
2129
+ function trigger() {
2130
+ return BgWidget.multiCheckboxContainer(
2131
+ gvc,
2132
+ [
2133
+ {
2134
+ key: 'auto',
2135
+ name: '自動折扣',
2136
+ innerHtml: BgWidget.grayNote('顧客將在結帳時自動獲得折扣'),
2137
+ },
2138
+ {
2139
+ key: 'code',
2140
+ name: '優惠代碼',
2141
+ innerHtml: (() => {
2142
+ const id = getUUID();
2143
+ return gvc.bindView({
2144
+ bind: id,
2145
+ view: () =>
2146
+ gvc.map([
2147
+ BgWidget.grayNote('顧客可在結帳時輸入優惠代碼,來獲得折扣'),
2148
+ BgWidget.editeInput({
2149
+ gvc: gvc,
2150
+ title: '',
2151
+ default: voucherData.code ?? '',
2152
+ placeHolder: '請輸入優惠券代碼',
2153
+ callback: text => {
2154
+ voucherData.code = text.toUpperCase();
2155
+ },
2156
+ endText: html` <div class="d-flex justify-content-end">
2157
+ ${BgWidget.mbContainer(8)}
2158
+ ${BgWidget.blueNote(
2159
+ document.body.clientWidth > 768 ? '隨機產生優惠代碼' : '隨機產生',
2160
+ gvc.event(() => {
2161
+ voucherData.code = Tool.randomString(6).toUpperCase();
2162
+ gvc.notifyDataChange(id);
2163
+ })
2164
+ )}
2165
+ </div>`,
2166
+ }),
2167
+ ]),
2168
+ });
2169
+ })(),
2170
+ },
2171
+ {
2172
+ key: 'distribution',
2173
+ name: '供特定賣場優惠使用',
2174
+ innerHtml: BgWidget.grayNote('僅限於隱形賣場 / 一頁商店 / 拼團賣場 / 分銷連結使用'),
2175
+ },
2176
+ ],
2177
+ [voucherData.trigger],
2178
+ text => {
2179
+ if (text[0] === 'auto') {
2180
+ voucherData.code = undefined;
2181
+ }
2182
+ if (text[0] === 'distribution') {
2183
+ voucherData.for = 'all';
2184
+ }
2185
+ voucherData.trigger = text[0] as Trigger;
2186
+ gvc.notifyDataChange(pageVM.viewID);
2187
+ },
2188
+ { single: true }
2189
+ );
2190
+ }
2191
+
2192
+ // 活動對象
2193
+ function target() {
2194
+ return gvc.bindView(() => {
2195
+ const id = getUUID();
2196
+ return {
2197
+ bind: id,
2198
+ view: () => {
2199
+ return html`
2200
+ <div style="display: flex; flex-direction: column; gap: 8px;">
2201
+ ${BgWidget.selectFilter({
2202
+ gvc: gvc,
2203
+ callback: text => {
2204
+ voucherData.target = text;
2205
+ gvc.notifyDataChange(id);
2206
+ },
2207
+ default: voucherData.target ?? 'all',
2208
+ options: [
2209
+ {
2210
+ key: 'all',
2211
+ value: '所有顧客',
2212
+ },
2213
+ {
2214
+ key: 'customer',
2215
+ value: '特定顧客',
2216
+ },
2217
+ {
2218
+ key: 'levels',
2219
+ value: '會員等級',
2220
+ },
2221
+ // {
2222
+ // key: 'group',
2223
+ // value: '顧客分群',
2224
+ // },
2225
+ ],
2226
+ style: 'width: 100%;',
2227
+ })}
2228
+ <div>
2229
+ ${(() => {
2230
+ switch (voucherData.target) {
2231
+ case 'all':
2232
+ return '';
2233
+ case 'customer':
2234
+ return gvc.bindView(() => {
2235
+ const customVM = {
2236
+ id: getUUID(),
2237
+ loading: true,
2238
+ dataList: [] as OptionsItem[],
2239
+ };
2240
+ return {
2241
+ bind: customVM.id,
2242
+ view: () => {
2243
+ if (customVM.loading) {
2244
+ return BgWidget.spinner();
2245
+ }
2246
+ return html`
2247
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
2248
+ <div
2249
+ class="d-flex align-items-center gray-bottom-line-18"
2250
+ style="justify-content: space-between;"
2251
+ >
2252
+ <div class="form-check-label c_updown_label">
2253
+ <div class="tx_normal">顧客名稱</div>
2254
+ </div>
2255
+ ${BgWidget.grayButton(
2256
+ '查看全部',
2257
+ gvc.event(() => {
2258
+ BgWidget.selectDropDialog({
2259
+ gvc: gvc,
2260
+ title: '搜尋特定顧客',
2261
+ tag: 'select_users',
2262
+ updownOptions: FilterOptions.userOrderBy,
2263
+ callback: value => {
2264
+ voucherData.targetList = value;
2265
+ customVM.loading = true;
2266
+ gvc.notifyDataChange(customVM.id);
2267
+ },
2268
+ default: (voucherData.targetList ?? []).map(id => id.toString()),
2269
+ api: (data: { query: string; orderString: string }) => {
2270
+ return new Promise(resolve => {
2271
+ ApiUser.getUserListOrders({
2272
+ page: 0,
2273
+ limit: 99999,
2274
+ search: data.query,
2275
+ orderString: data.orderString,
2276
+ only_id: true,
2277
+ }).then(dd => {
2278
+ if (dd.response.data) {
2279
+ resolve(
2280
+ dd.response.data.map(
2281
+ (item: {
2282
+ userID: number;
2283
+ userData: {
2284
+ name: string;
2285
+ email: string;
2286
+ };
2287
+ }) => {
2288
+ return {
2289
+ key: item.userID,
2290
+ value: item.userData.name ?? '(尚無姓名)',
2291
+ note: item.userData.email,
2292
+ };
2293
+ }
2294
+ )
2295
+ );
2296
+ }
2297
+ });
2298
+ });
2299
+ },
2300
+ style: 'width: 100%;',
2301
+ });
2302
+ }),
2303
+ { textStyle: 'font-weight: 400;' }
2304
+ )}
2305
+ </div>
2306
+ ${obj.gvc.map(
2307
+ customVM.dataList.map((opt: OptionsItem, index) => {
2308
+ return html` <div class="form-check-label c_updown_label">
2309
+ <span class="tx_normal">${index + 1}. ${opt.value}</span>
2310
+ ${opt.note ? html` <span class="tx_gray_12 ms-2">${opt.note}</span> ` : ''}
2311
+ </div>`;
2312
+ })
2313
+ )}
2314
+ </div>
2315
+ `;
2316
+ },
2317
+ onCreate: () => {
2318
+ if (customVM.loading) {
2319
+ if (voucherData.targetList.length === 0) {
2320
+ setTimeout(() => {
2321
+ customVM.dataList = [];
2322
+ customVM.loading = false;
2323
+ gvc.notifyDataChange(customVM.id);
2324
+ }, 200);
2325
+ } else {
2326
+ ApiUser.getUserList({
2327
+ page: 0,
2328
+ limit: 99999,
2329
+ id: voucherData.targetList.join(','),
2330
+ only_id: true,
2331
+ }).then(dd => {
2332
+ if (dd.response.data) {
2333
+ customVM.dataList = dd.response.data.map(
2334
+ (item: {
2335
+ userID: string;
2336
+ userData: {
2337
+ name: string;
2338
+ email: string;
2339
+ };
2340
+ }) => {
2341
+ return {
2342
+ key: item.userID,
2343
+ value: item.userData.name,
2344
+ note: item.userData.email,
2345
+ };
2346
+ }
2347
+ );
2348
+ }
2349
+ customVM.loading = false;
2350
+ gvc.notifyDataChange(customVM.id);
2351
+ });
2352
+ }
2353
+ }
2354
+ },
2355
+ };
2356
+ });
2357
+ case 'levels':
2358
+ return (() => {
2359
+ const levelVM = {
2360
+ id: getUUID(),
2361
+ loading: true,
2362
+ dataList: [],
2363
+ };
2364
+ return gvc.bindView({
2365
+ bind: levelVM.id,
2366
+ view: () => {
2367
+ if (levelVM.loading) {
2368
+ return BgWidget.spinner({ text: { visible: false } });
2369
+ } else {
2370
+ return BgWidget.selectDropList({
2371
+ gvc: gvc,
2372
+ callback: value => {
2373
+ voucherData.targetList = value;
2374
+ gvc.notifyDataChange(id);
2375
+ },
2376
+ default: (voucherData.targetList ?? []).map(id => id.toString()),
2377
+ options: levelVM.dataList,
2378
+ style: 'width: 100%;',
2379
+ });
2380
+ }
2381
+ },
2382
+ divCreate: {
2383
+ style: 'width: 100%;',
2384
+ },
2385
+ onCreate: () => {
2386
+ if (levelVM.loading) {
2387
+ ApiUser.getPublicConfig('member_level_config', 'manager').then((dd: any) => {
2388
+ if (dd.result && dd.response.value) {
2389
+ levelVM.dataList = dd.response.value.levels.map(
2390
+ (item: { id: string; tag_name: string }) => {
2391
+ return {
2392
+ key: item.id,
2393
+ value: item.tag_name,
2394
+ // note.txt: '人數'
2395
+ };
2396
+ }
2397
+ );
2398
+ levelVM.loading = false;
2399
+ gvc.notifyDataChange(levelVM.id);
2400
+ }
2401
+ });
2402
+ }
2403
+ },
2404
+ });
2405
+ })();
2406
+ case 'group':
2407
+ return (() => {
2408
+ const levelVM = {
2409
+ id: getUUID(),
2410
+ loading: true,
2411
+ dataList: [],
2412
+ };
2413
+ return gvc.bindView({
2414
+ bind: levelVM.id,
2415
+ view: () => {
2416
+ if (levelVM.loading) {
2417
+ return BgWidget.spinner({ text: { visible: false } });
2418
+ } else {
2419
+ return BgWidget.selectDropList({
2420
+ gvc: gvc,
2421
+ callback: (value: []) => {
2422
+ voucherData.targetList = value;
2423
+ gvc.notifyDataChange(id);
2424
+ },
2425
+ default: (voucherData.targetList ?? []).map(id => id.toString()),
2426
+ options: levelVM.dataList,
2427
+ style: 'width: 100%;',
2428
+ });
2429
+ }
2430
+ },
2431
+ divCreate: {
2432
+ style: 'width: 100%;',
2433
+ },
2434
+ onCreate: () => {
2435
+ if (levelVM.loading) {
2436
+ ApiUser.getUserGroupList().then((dd: any) => {
2437
+ if (dd.result && dd.response.data) {
2438
+ levelVM.dataList = dd.response.data
2439
+ .filter((item: any) => {
2440
+ return item.type !== 'level';
2441
+ })
2442
+ .map((item: any) => {
2443
+ return {
2444
+ key: item.type,
2445
+ value: item.title,
2446
+ };
2447
+ });
2448
+ levelVM.loading = false;
2449
+ gvc.notifyDataChange(levelVM.id);
2450
+ }
2451
+ });
2452
+ }
2453
+ },
2454
+ });
2455
+ })();
2456
+ default:
2457
+ return '';
2458
+ }
2459
+ })()}
2460
+ </div>
2461
+ </div>
2462
+ `;
2463
+ },
2464
+ };
2465
+ });
2466
+ }
2467
+
2468
+ // 適用訂單類型
2469
+ function device() {
2470
+ return BgWidget.multiCheckboxContainer(
2471
+ gvc,
2472
+ [
2473
+ { key: 'normal', name: '官網 & APP 訂單' },
2474
+ { key: 'pos', name: 'POS 訂單' },
2475
+ ],
2476
+ voucherData.device ?? ['normal'],
2477
+ text => {
2478
+ voucherData.device = text as Device[];
2479
+ gvc.notifyDataChange(pageVM.viewID);
2480
+ },
2481
+ { single: false }
2482
+ );
2483
+ }
2484
+
2485
+ // 可使用物流
2486
+ function selectShipment() {
2487
+ if (voucherData.reBackType !== 'shipment_free') {
2488
+ return '';
2489
+ }
2490
+
2491
+ const id = getUUID();
2492
+ return gvc.bindView({
2493
+ bind: id,
2494
+ view: () => {
2495
+ return [
2496
+ BgWidget.select({
2497
+ gvc,
2498
+ callback: value => {
2499
+ voucherData.selectShipment.type = value as SelectShipmentType;
2500
+ gvc.notifyDataChange(id);
2501
+ },
2502
+ default: voucherData.selectShipment.type,
2503
+ options: [
2504
+ { key: 'all', value: '所有物流' },
2505
+ { key: 'select', value: '指定物流' },
2506
+ ],
2507
+ }),
2508
+ voucherData.selectShipment.type === 'all'
2509
+ ? ''
2510
+ : BgWidget.selectDropList({
2511
+ gvc: gvc,
2512
+ callback: value => {
2513
+ voucherData.selectShipment.list = value;
2514
+ },
2515
+ default: voucherData.selectShipment.list ?? [],
2516
+ options: ShipmentConfig.list.map(item => {
2517
+ return {
2518
+ key: item.value,
2519
+ value: item.title,
2520
+ };
2521
+ }),
2522
+ style: 'width: 100%;',
2523
+ }),
2524
+ ]
2525
+ .filter(Boolean)
2526
+ .join(BgWidget.mbContainer(8));
2527
+ },
2528
+ });
2529
+ }
2530
+
2531
+ // 優惠套用至
2532
+ function setVoucherFor() {
2533
+ return EditorElem.radio({
2534
+ gvc: gvc,
2535
+ title: '',
2536
+ def: voucherData.for ?? 'all',
2537
+ array: ShoppingDiscountSetting.productForList,
2538
+ callback: text => {
2539
+ voucherData.forKey = defKeys[text as VoucherForType];
2540
+ voucherData.for = text as VoucherForType;
2541
+ gvc.notifyDataChange(pageVM.viewID);
2542
+ },
2543
+ oneLine: true,
2544
+ });
2545
+ }
2546
+
2547
+ // 活動商品
2548
+ function selectProduct() {
2549
+ if (voucherData.trigger === 'distribution') {
2550
+ return '';
2551
+ }
2552
+
2553
+ switch (voucherData.for) {
2554
+ case 'manager_tag':
2555
+ return gvc.bindView(() => {
2556
+ const subVM = {
2557
+ id: getUUID(),
2558
+ dataList: String(voucherData.for) === 'manager_tag' ? [...defKeys.manager_tag] : [],
2559
+ };
2560
+
2561
+ return {
2562
+ bind: subVM.id,
2563
+ view: () => {
2564
+ return html`
2565
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
2566
+ <div
2567
+ class="d-flex align-items-center gray-bottom-line-18"
2568
+ style="gap: 24px; justify-content: space-between;"
2569
+ >
2570
+ <div class="form-check-label c_updown_label">
2571
+ <div class="tx_normal">標籤列表</div>
2572
+ </div>
2573
+ ${BgWidget.grayButton(
2574
+ '選擇標籤',
2575
+ gvc.event(() => {
2576
+ BgProduct.useProductTags({
2577
+ gvc,
2578
+ config_key: 'product_manager_tags',
2579
+ def:
2580
+ String(voucherData.for) === 'manager_tag' && voucherData.forKey
2581
+ ? voucherData.forKey.map(item => `${item}`)
2582
+ : [],
2583
+ callback: async value => {
2584
+ voucherData.forKey = value;
2585
+ defKeys.manager_tag = value;
2586
+ subVM.dataList = value;
2587
+ gvc.notifyDataChange(subVM.id);
2588
+ },
2589
+ });
2590
+ }),
2591
+ { textStyle: 'font-weight: 400;' }
2592
+ )}
2593
+ </div>
2594
+ ${obj.gvc.map(
2595
+ subVM.dataList.map((opt, index) => {
2596
+ return html` <div class="d-flex align-items-center form-check-label c_updown_label gap-3">
2597
+ <span class="tx_normal">${index + 1}. #${opt}</span>
2598
+ </div>`;
2599
+ })
2600
+ )}
2601
+ </div>
2602
+ `;
2603
+ },
2604
+ };
2605
+ });
2606
+ case 'collection':
2607
+ return gvc.bindView(() => {
2608
+ const subVM = {
2609
+ id: getUUID(),
2610
+ loading: true,
2611
+ dataList: [] as OptionsItem[],
2612
+ };
2613
+ return {
2614
+ bind: subVM.id,
2615
+ view: () => {
2616
+ if (subVM.loading) {
2617
+ return BgWidget.spinner();
2618
+ }
2619
+ return html`
2620
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
2621
+ <div
2622
+ class="d-flex align-items-center gray-bottom-line-18"
2623
+ style="gap: 24px; justify-content: space-between;"
2624
+ >
2625
+ <div class="form-check-label c_updown_label">
2626
+ <div class="tx_normal">分類列表</div>
2627
+ </div>
2628
+ ${BgWidget.grayButton(
2629
+ '選擇分類',
2630
+ gvc.event(() => {
2631
+ BgProduct.collectionsDialog({
2632
+ gvc: gvc,
2633
+ default: voucherData.forKey ?? [],
2634
+ callback: async value => {
2635
+ voucherData.forKey = value;
2636
+ defKeys.collection = value;
2637
+ subVM.dataList = await BgProduct.getCollectiosOpts(value);
2638
+ subVM.loading = true;
2639
+ gvc.notifyDataChange(subVM.id);
2640
+ },
2641
+ });
2642
+ }),
2643
+ { textStyle: 'font-weight: 400;' }
2644
+ )}
2645
+ </div>
2646
+ ${obj.gvc.map(
2647
+ subVM.dataList.map((opt: OptionsItem, index) => {
2648
+ return html` <div class="d-flex align-items-center form-check-label c_updown_label gap-3">
2649
+ <span class="tx_normal">${index + 1}. ${opt.value}</span>
2650
+ ${opt.note ? html` <span class="tx_gray_12 ms-2">${opt.note}</span> ` : ''}
2651
+ </div>`;
2652
+ })
2653
+ )}
2654
+ </div>
2655
+ `;
2656
+ },
2657
+ onCreate: () => {
2658
+ if (subVM.loading) {
2659
+ if (voucherData.forKey.length === 0) {
2660
+ setTimeout(() => {
2661
+ subVM.dataList = [];
2662
+ subVM.loading = false;
2663
+ gvc.notifyDataChange(subVM.id);
2664
+ }, 200);
2665
+ } else {
2666
+ new Promise<OptionsItem[]>(resolve => {
2667
+ resolve(BgProduct.getCollectiosOpts(voucherData.forKey));
2668
+ }).then(data => {
2669
+ subVM.dataList = data;
2670
+ subVM.loading = false;
2671
+ gvc.notifyDataChange(subVM.id);
2672
+ });
2673
+ }
2674
+ }
2675
+ },
2676
+ };
2677
+ });
2678
+ case 'product':
2679
+ return gvc.bindView(() => {
2680
+ const subVM = {
2681
+ id: getUUID(),
2682
+ loading: true,
2683
+ dataList: [] as OptionsItem[],
2684
+ };
2685
+ return {
2686
+ bind: subVM.id,
2687
+ view: () => {
2688
+ if (subVM.loading) {
2689
+ return BgWidget.spinner();
2690
+ }
2691
+ return html`
2692
+ <div class="d-flex flex-column p-2" style="gap: 18px;">
2693
+ <div
2694
+ class="d-flex align-items-center gray-bottom-line-18"
2695
+ style="gap: 24px; justify-content: space-between;"
2696
+ >
2697
+ <div class="form-check-label c_updown_label">
2698
+ <div class="tx_normal">商品列表</div>
2699
+ </div>
2700
+ ${BgWidget.grayButton(
2701
+ '選擇商品',
2702
+ gvc.event(() => {
2703
+ BgProduct.productsDialog({
2704
+ gvc: gvc,
2705
+ default: voucherData.forKey ?? [],
2706
+ callback: async value => {
2707
+ voucherData.forKey = value;
2708
+ defKeys.product = value;
2709
+ subVM.dataList = await BgProduct.getProductOpts(voucherData.forKey);
2710
+ subVM.loading = true;
2711
+ gvc.notifyDataChange(subVM.id);
2712
+ },
2713
+ });
2714
+ }),
2715
+ { textStyle: 'font-weight: 400;' }
2716
+ )}
2717
+ </div>
2718
+ ${subVM.dataList
2719
+ .map((opt: OptionsItem, index) => {
2720
+ return html` <div class="d-flex align-items-center form-check-label c_updown_label gap-3">
2721
+ <span class="tx_normal">${index + 1}.</span>
2722
+ ${BgWidget.validImageBox({
2723
+ gvc: gvc,
2724
+ image: opt.image,
2725
+ width: 40,
2726
+ })}
2727
+ <div class="tx_normal ${opt.note ? 'mb-1' : ''}">${opt.value}</div>
2728
+ ${opt.note ? html` <div class="tx_gray_12">${opt.note}</div> ` : ''}
2729
+ </div>`;
2730
+ })
2731
+ .join(``)}
2732
+ </div>
2733
+ `;
2734
+ },
2735
+ onCreate: () => {
2736
+ if (subVM.loading) {
2737
+ if (voucherData.forKey.length === 0) {
2738
+ setTimeout(() => {
2739
+ subVM.dataList = [];
2740
+ subVM.loading = false;
2741
+ gvc.notifyDataChange(subVM.id);
2742
+ }, 200);
2743
+ } else {
2744
+ new Promise<OptionsItem[]>(resolve => {
2745
+ resolve(BgProduct.getProductOpts(voucherData.forKey));
2746
+ }).then(data => {
2747
+ subVM.dataList = data;
2748
+ subVM.loading = false;
2749
+ gvc.notifyDataChange(subVM.id);
2750
+ });
2751
+ }
2752
+ }
2753
+ },
2754
+ };
2755
+ });
2756
+ case 'all':
2757
+ return '';
2758
+ }
2759
+ }
2760
+
2761
+ // 加購商品 function
2762
+ function addProductView() {
2763
+ return obj.gvc.bindView(() => {
2764
+ const id = getUUID();
2765
+ return {
2766
+ bind: id,
2767
+ view: () => {
2768
+ try {
2769
+ return html`
2770
+ <div
2771
+ class="d-flex align-items-center gray-bottom-line-18"
2772
+ style="gap: 24px; justify-content: space-between;"
2773
+ >
2774
+ <div class="form-check-label c_updown_label">
2775
+ <div class="tx_normal">商品列表</div>
2776
+ </div>
2777
+ ${BgWidget.grayButton(
2778
+ '選擇商品',
2779
+ gvc.event(() => {
2780
+ BgProduct.productsDialog({
2781
+ gvc: gvc,
2782
+ default: voucherData.add_on_products ?? [],
2783
+ callback: async value => {
2784
+ voucherData.add_on_products = value;
2785
+ gvc.notifyDataChange(id);
2786
+ },
2787
+ filter: dd => {
2788
+ return true;
2789
+ },
2790
+ productType: voucherData.reBackType === 'add_on_items' ? 'addProduct' : 'giveaway',
2791
+ });
2792
+ }),
2793
+ { textStyle: 'font-weight: 400;' }
2794
+ )}
2795
+ </div>
2796
+ ${gvc.bindView(() => {
2797
+ const vm: {
2798
+ viewID: string;
2799
+ loading: boolean;
2800
+ data: OptionsItem[];
2801
+ } = {
2802
+ viewID: getUUID(),
2803
+ loading: true,
2804
+ data: [],
2805
+ };
2806
+ BgProduct.getProductOpts(
2807
+ voucherData.add_on_products!,
2808
+ voucherData.reBackType === 'add_on_items' ? 'addProduct' : 'giveaway'
2809
+ ).then(res => {
2810
+ vm.data = res;
2811
+ vm.loading = false;
2812
+ gvc.notifyDataChange(vm.viewID);
2813
+ });
2814
+ return {
2815
+ bind: vm.viewID,
2816
+ view: async () => {
2817
+ if (vm.loading) {
2818
+ return BgWidget.spinner();
2819
+ }
2820
+ return vm.data
2821
+ .map((opt: OptionsItem, index) => {
2822
+ return html` <div class="d-flex align-items-center form-check-label c_updown_label gap-3">
2823
+ <span class="tx_normal">${index + 1}.</span>
2824
+ ${BgWidget.validImageBox({
2825
+ gvc: gvc,
2826
+ image: opt.image,
2827
+ width: 40,
2828
+ })}
2829
+ <div class="tx_normal ${opt.note ? 'mb-1' : ''}">${opt.value}</div>
2830
+ ${opt.note ? html` <div class="tx_gray_12">${opt.note}</div> ` : ''}
2831
+ </div>`;
2832
+ })
2833
+ .join('');
2834
+ },
2835
+ divCreate: {
2836
+ class: `d-flex py-2 flex-column`,
2837
+ style: `gap:10px;`,
2838
+ },
2839
+ };
2840
+ })}
2841
+ `;
2842
+ } catch (e) {
2843
+ console.error(e);
2844
+ return '';
2845
+ }
2846
+ },
2847
+ divCreate: {
2848
+ class: `w-100`,
2849
+ },
2850
+ };
2851
+ });
2852
+ }
2853
+
2854
+ // 全館總使用次數
2855
+ function storeUseTimeLimit() {
2856
+ return BgWidget.multiCheckboxContainer(
2857
+ gvc,
2858
+ [
2859
+ {
2860
+ key: 'noLimited',
2861
+ name: '無限制',
2862
+ },
2863
+ {
2864
+ key: 'hasLimited',
2865
+ name: '限制次數',
2866
+ innerHtml: html` <div class="d-flex align-items-center">
2867
+ <span class="tx_normal me-2">可使用次數</span>
2868
+ ${BgWidget.editeInput({
2869
+ gvc: gvc,
2870
+ title: '',
2871
+ type: 'number',
2872
+ divStyle: 'width: 150px;',
2873
+ default: `${voucherData.macroLimited ?? 0}`,
2874
+ placeHolder: '',
2875
+ callback: text => {
2876
+ voucherData.macroLimited = parseInt(text, 10);
2877
+ },
2878
+ endText: '次',
2879
+ })}
2880
+ </div>`,
2881
+ },
2882
+ ],
2883
+ [voucherData.macroLimited === 0 ? 'noLimited' : 'hasLimited'],
2884
+ text => {
2885
+ if (text[0] === 'noLimited') {
2886
+ voucherData.macroLimited = 0;
2887
+ }
2888
+ },
2889
+ { single: true }
2890
+ );
2891
+ }
2892
+
2893
+ // 個人總使用次數
2894
+ function memberUseTimeLimit() {
2895
+ return BgWidget.multiCheckboxContainer(
2896
+ gvc,
2897
+ [
2898
+ {
2899
+ key: 'noLimited',
2900
+ name: '無限制',
2901
+ },
2902
+ {
2903
+ key: 'hasLimited',
2904
+ name: '限制次數',
2905
+ innerHtml: html` <div class="d-flex align-items-center">
2906
+ <span class="tx_normal me-2">可使用次數</span>
2907
+ ${BgWidget.editeInput({
2908
+ gvc: gvc,
2909
+ title: '',
2910
+ type: 'number',
2911
+ divStyle: 'width: 150px;',
2912
+ default: `${voucherData.microLimited ?? 0}`,
2913
+ placeHolder: '',
2914
+ callback: text => {
2915
+ voucherData.microLimited = parseInt(text, 10);
2916
+ },
2917
+ endText: '次',
2918
+ })}
2919
+ </div>`,
2920
+ },
2921
+ ],
2922
+ [voucherData.microLimited === 0 ? 'noLimited' : 'hasLimited'],
2923
+ text => {
2924
+ if (text[0] === 'noLimited') {
2925
+ voucherData.microLimited = 0;
2926
+ }
2927
+ },
2928
+ { single: true }
2929
+ );
2930
+ }
2931
+
2932
+ // 優惠卷有效日期
2933
+ function startDateTime() {
2934
+ const inputStyle = 'display: block; width: 200px;';
2935
+ return html` <div
2936
+ class="d-flex mb-2 ${document.body.clientWidth < 768 ? 'flex-column' : ''}"
2937
+ style="gap: 12px"
2938
+ >
2939
+ <div class="d-flex align-items-center">
2940
+ <span class="tx_normal me-2">開始日期</span>
2941
+ ${BgWidget.editeInput({
2942
+ gvc: gvc,
2943
+ title: '',
2944
+ type: 'date',
2945
+ style: inputStyle,
2946
+ default: `${voucherData.startDate}`,
2947
+ placeHolder: '',
2948
+ callback: text => {
2949
+ voucherData.startDate = text;
2950
+ },
2951
+ })}
2952
+ </div>
2953
+ <div class="d-flex align-items-center">
2954
+ <span class="tx_normal me-2">開始時間</span>
2955
+ ${BgWidget.editeInput({
2956
+ gvc: gvc,
2957
+ title: '',
2958
+ type: 'time',
2959
+ style: inputStyle,
2960
+ default: `${voucherData.startTime}`,
2961
+ placeHolder: '',
2962
+ callback: text => {
2963
+ voucherData.startTime = text;
2964
+ },
2965
+ })}
2966
+ </div>
2967
+ </div>
2968
+ ${BgWidget.multiCheckboxContainer(
2969
+ gvc,
2970
+ [
2971
+ {
2972
+ key: 'noEnd',
2973
+ name: '無期限',
2974
+ },
2975
+ {
2976
+ key: 'withEnd',
2977
+ name: '有效期限',
2978
+ innerHtml: html` <div
2979
+ class="d-flex mt-0 mt-md-3 ${document.body.clientWidth < 768 ? 'flex-column' : ''}"
2980
+ style="gap: 12px"
2981
+ >
2982
+ <div class="d-flex align-items-center">
2983
+ <span class="tx_normal me-2">結束日期</span>
2984
+ ${BgWidget.editeInput({
2985
+ gvc: gvc,
2986
+ title: '',
2987
+ type: 'date',
2988
+ style: inputStyle,
2989
+ default: `${voucherData.endDate}`,
2990
+ placeHolder: '',
2991
+ callback: text => {
2992
+ voucherData.endDate = text;
2993
+ },
2994
+ })}
2995
+ </div>
2996
+ <div class="d-flex align-items-center">
2997
+ <span class="tx_normal me-2">結束時間</span>
2998
+ ${BgWidget.editeInput({
2999
+ gvc: gvc,
3000
+ title: '',
3001
+ type: 'time',
3002
+ style: inputStyle,
3003
+ default: `${voucherData.endTime}`,
3004
+ placeHolder: '',
3005
+ callback: text => {
3006
+ voucherData.endTime = text;
3007
+ },
3008
+ })}
3009
+ </div>
3010
+ </div>`,
3011
+ },
3012
+ ],
3013
+ [voucherData.endDate ? `withEnd` : `noEnd`],
3014
+ text => {
3015
+ if (text[0] === 'noEnd') {
3016
+ voucherData.endDate = undefined;
3017
+ voucherData.endTime = undefined;
3018
+ }
3019
+ },
3020
+ { single: true }
3021
+ )}`;
3022
+ }
3023
+
3024
+ // 購物金有效天數
3025
+ function rebateEndDay() {
3026
+ if (voucherData.reBackType !== 'rebate') {
3027
+ return '';
3028
+ }
3029
+
3030
+ const inputStyle = 'display: block; width: 200px;';
3031
+ return BgWidget.multiCheckboxContainer(
3032
+ gvc,
3033
+ [
3034
+ {
3035
+ key: 'noEnd',
3036
+ name: '無期限',
3037
+ },
3038
+ {
3039
+ key: 'withEnd',
3040
+ name: '有效期限',
3041
+ innerHtml: html` <div
3042
+ class="d-flex mt-0 mt-md-3 ${document.body.clientWidth < 768 ? 'flex-column' : ''}"
3043
+ style="gap: 12px"
3044
+ >
3045
+ <div class="d-flex align-items-center" style="gap:10px;">
3046
+ ${BgWidget.editeInput({
3047
+ gvc: gvc,
3048
+ title: '',
3049
+ type: 'number',
3050
+ style: inputStyle,
3051
+ default: `${voucherData.rebateEndDay ?? ''}`,
3052
+ placeHolder: '0則為無期限',
3053
+ callback: text => {
3054
+ voucherData.rebateEndDay = text;
3055
+ gvc.notifyDataChange(pageVM.viewID);
3056
+ },
3057
+ })}
3058
+ <span class="tx_normal me-2">天</span>
3059
+ </div>
3060
+ </div>`,
3061
+ },
3062
+ ],
3063
+ [parseInt(voucherData.rebateEndDay ?? '0', 10) ? `withEnd` : `noEnd`],
3064
+ text => {
3065
+ if (text[0] === 'noEnd') {
3066
+ voucherData.rebateEndDay = '0';
3067
+ }
3068
+ },
3069
+ { single: true }
3070
+ );
3071
+ }
3072
+
3073
+ // 是否與其他優惠券疊加使用
3074
+ function overlay() {
3075
+ return BgWidget.multiCheckboxContainer(
3076
+ gvc,
3077
+ [
3078
+ {
3079
+ key: 'false',
3080
+ name: '不可疊加',
3081
+ innerHtml: BgWidget.grayNote('系統將以最大優惠排序進行判定'),
3082
+ },
3083
+ {
3084
+ key: 'true',
3085
+ name: '可以疊加',
3086
+ },
3087
+ ],
3088
+ [voucherData.overlay ? 'true' : 'false'],
3089
+ text => {
3090
+ voucherData.overlay = text[0] === 'true';
3091
+ gvc.notifyDataChange(pageVM.viewID);
3092
+ },
3093
+ { single: true }
3094
+ );
3095
+ }
3096
+
3097
+ // 打折範圍
3098
+ function offStart() {
3099
+ const bool =
3100
+ voucherData.method === 'percent' &&
3101
+ voucherData.conditionType === 'order' &&
3102
+ voucherData.rule === 'min_count' &&
3103
+ voucherData.reBackType === 'discount';
3104
+
3105
+ if (!bool) {
3106
+ return '';
3107
+ }
3108
+
3109
+ return BgWidget.multiCheckboxContainer(
3110
+ gvc,
3111
+ [
3112
+ {
3113
+ key: 'price_desc',
3114
+ name: '從最高價的商品打折',
3115
+ innerHtml: BgWidget.grayNote(`購物車訂單將會從最高價且符合優惠的${ruleText(ruleValue)}商品進行打折`),
3116
+ },
3117
+ {
3118
+ key: 'price_asc',
3119
+ name: '從最低價的商品打折',
3120
+ innerHtml: BgWidget.grayNote(`購物車訂單將會從最低價且符合優惠的${ruleText(ruleValue)}商品進行打折`),
3121
+ },
3122
+ {
3123
+ key: 'price_all',
3124
+ name: '符合優惠的商品全部打折',
3125
+ innerHtml: BgWidget.grayNote(`購物車訂單符合優惠的商品進行打折`),
3126
+ },
3127
+ ],
3128
+ [voucherData.productOffStart],
3129
+ text => {
3130
+ voucherData.productOffStart = text[0] as 'price_asc' | 'price_desc' | 'price_all';
3131
+ gvc.notifyDataChange(pageVM.viewID);
3132
+ },
3133
+ {
3134
+ single: true,
3135
+ }
3136
+ );
3137
+ }
3138
+
3139
+ // 消費金額於其他折扣觸發時機
3140
+ function isIncludeDiscount() {
3141
+ if (!voucherData.overlay) {
3142
+ return '';
3143
+ }
3144
+
3145
+ return BgWidget.multiCheckboxContainer(
3146
+ gvc,
3147
+ [
3148
+ {
3149
+ key: 'before',
3150
+ name: '觸發前',
3151
+ innerHtml: BgWidget.grayNote(
3152
+ '在其他折扣觸發前,訂單的消費金額將做為達成消費條件的金額,來判斷是否可使用此優惠券'
3153
+ ),
3154
+ },
3155
+ {
3156
+ key: 'after',
3157
+ name: '觸發後',
3158
+ innerHtml: BgWidget.grayNote(
3159
+ '將訂單的消費金額包含其他折扣後,做為達成消費條件的金額,來判斷是否可使用此優惠券'
3160
+ ),
3161
+ },
3162
+ ],
3163
+ [voucherData.includeDiscount],
3164
+ text => {
3165
+ voucherData.includeDiscount = text[0] as IncludeDiscount;
3166
+ gvc.notifyDataChange(pageVM.viewID);
3167
+ },
3168
+ {
3169
+ single: true,
3170
+ }
3171
+ );
3172
+ }
3173
+
3174
+ // 重複觸發
3175
+ function countingBoolean() {
3176
+ if (voucherData.method === 'percent' || voucherData.reBackType === 'shipment_free') {
3177
+ return '';
3178
+ }
3179
+
3180
+ return BgWidget.multiCheckboxContainer(
3181
+ gvc,
3182
+ [
3183
+ {
3184
+ key: 'single',
3185
+ name: '不重複',
3186
+ innerHtml: BgWidget.grayNote(
3187
+ `購買${ruleText(ruleValue)}折Y元,額外購買至${ruleText(ruleValue * 2)}或${ruleText(ruleValue * 3)}依然是折Y元`
3188
+ ),
3189
+ },
3190
+ {
3191
+ key: 'each',
3192
+ name: '重複',
3193
+ innerHtml: BgWidget.grayNote(
3194
+ `購買${ruleText(ruleValue)}折Y元,購買${ruleText(ruleValue * 2)}則折Y * 2元,購買${ruleText(ruleValue * 3)}則折Y * 3元,以此類推`
3195
+ ),
3196
+ },
3197
+ ],
3198
+ [voucherData.counting],
3199
+ text => {
3200
+ voucherData.counting = text[0] as Counting;
3201
+ gvc.notifyDataChange(pageVM.viewID);
3202
+ },
3203
+ {
3204
+ single: true,
3205
+ }
3206
+ );
3207
+ }
3208
+
3209
+ // 計算單位
3210
+ function conditionType() {
3211
+ return BgWidget.multiCheckboxContainer(
3212
+ gvc,
3213
+ [
3214
+ {
3215
+ key: 'order',
3216
+ name: '以整份訂單計算',
3217
+ innerHtml: BgWidget.grayNote(
3218
+ (() => {
3219
+ if (voucherData.reBackType === 'shipment_free') {
3220
+ return '優惠條件為整份訂單免運費';
3221
+ }
3222
+ return `若商品A購買${ruleText(floor)},加上商品B購買${ruleText(ruleValue - floor)},可觸發優惠`;
3223
+ })()
3224
+ ),
3225
+ },
3226
+ {
3227
+ key: 'item',
3228
+ name: '以商品計算',
3229
+ innerHtml: BgWidget.grayNote(
3230
+ `需要商品A購買滿${ruleText(ruleValue)},或商品B購買滿${ruleText(ruleValue)},即可觸發優惠<br/>若商品A購買${ruleText(
3231
+ floor
3232
+ )},加上商品B購買${ruleText(ruleValue - floor)},無法觸發優惠`
3233
+ ),
3234
+ },
3235
+ ],
3236
+ [voucherData.conditionType],
3237
+ text => {
3238
+ voucherData.conditionType = text[0] as ConditionType;
3239
+ gvc.notifyDataChange(pageVM.viewID);
3240
+ },
3241
+ {
3242
+ single: true,
3243
+ readonly: voucherData.reBackType === 'shipment_free',
3244
+ }
3245
+ );
3246
+ }
3247
+
3248
+ // 折扣設定
3249
+ function method() {
3250
+ if (['shipment_free', 'add_on_items', 'giveaway'].includes(voucherData.reBackType)) {
3251
+ return '';
3252
+ }
3253
+ return BgWidget.multiCheckboxContainer(
3254
+ gvc,
3255
+ [
3256
+ {
3257
+ key: 'fixed',
3258
+ name: '固定金額',
3259
+ innerHtml: valueInput({ startText: '$' }),
3260
+ },
3261
+ {
3262
+ key: 'percent',
3263
+ name: '百分比',
3264
+ innerHtml: valueInput({ endText: '%' }),
3265
+ },
3266
+ ],
3267
+ [voucherData.method],
3268
+ text => {
3269
+ voucherData.value = '0';
3270
+ voucherData.method = text[0] as Method;
3271
+ gvc.notifyDataChange(pageVM.viewID);
3272
+ },
3273
+ { single: true }
3274
+ );
3275
+ }
3276
+
3277
+ // 計算單位用 Input
3278
+ function conditionInput(text: string) {
3279
+ return BgWidget.editeInput({
3280
+ gvc: gvc,
3281
+ title: '',
3282
+ divStyle: 'width: 150px;',
3283
+ default: `${voucherData.ruleValue ?? 0}`,
3284
+ placeHolder: '',
3285
+ callback: value => {
3286
+ voucherData.ruleValue = parseInt(value, 10);
3287
+ gvc.notifyDataChange(pageVM.viewID);
3288
+ },
3289
+ endText: text,
3290
+ });
3291
+ }
3292
+
3293
+ // 使用條件
3294
+ function rule() {
3295
+ return BgWidget.multiCheckboxContainer(
3296
+ gvc,
3297
+ [
3298
+ {
3299
+ key: 'min_price',
3300
+ name: '最低消費金額',
3301
+ innerHtml: conditionInput('元'),
3302
+ },
3303
+ {
3304
+ key: 'min_count',
3305
+ name: '最少購買數量',
3306
+ innerHtml: conditionInput('個'),
3307
+ },
3308
+ ],
3309
+ [voucherData.rule],
3310
+ text => {
3311
+ voucherData.ruleValue = 0;
3312
+ voucherData.rule = text[0] as Rule;
3313
+ gvc.notifyDataChange(pageVM.viewID);
3314
+ },
3315
+ { single: true }
3316
+ );
3317
+ }
3318
+
3319
+ // 單位文字
3320
+ function ruleText(sum: number) {
3321
+ return `${sum}${voucherData.rule === 'min_count' ? '個' : '元'}`;
3322
+ }
3323
+
3324
+ // 數字 Input
3325
+ function valueInput(obj: { startText?: string; endText?: string }) {
3326
+ return BgWidget.editeInput({
3327
+ gvc: gvc,
3328
+ type: 'number',
3329
+ divStyle: 'width: 150px;',
3330
+ title: '',
3331
+ default: voucherData.value,
3332
+ placeHolder: '',
3333
+ callback: text => {
3334
+ const texInt = parseInt(text, 10);
3335
+ if (voucherData.method === 'percent' && (texInt > 100 || texInt < 0)) {
3336
+ dialog.infoMessage({ text: '數值需介於0~100' });
3337
+ gvc.notifyDataChange(pageVM.viewID);
3338
+ } else {
3339
+ voucherData.value = text;
3340
+ }
3341
+ },
3342
+ startText: obj.startText,
3343
+ endText: obj.endText,
3344
+ });
3345
+ }
3346
+
3347
+ // 優惠促銷模式的選取商品畫面
3348
+ const reBackProductView: Record<RebackType, { title: string; html: string | string[] }> = {
3349
+ rebate: { title: '活動商品', html: voucherData.for === 'all' ? '' : selectProduct() },
3350
+ discount: { title: '活動商品', html: voucherData.for === 'all' ? '' : selectProduct() },
3351
+ shipment_free: { title: '活動商品', html: '' },
3352
+ add_on_items: { title: '加購品項', html: addProductView() },
3353
+ giveaway: { title: '贈品品項', html: addProductView() },
3354
+ };
3355
+
3356
+ // 優惠券設定 Layout Data
3357
+ const viewList: { title: string; html: string | string[] }[][] = [
3358
+ [
3359
+ { title: '活動設定', html: [status(), title()] },
3360
+ { title: '折扣方式', html: trigger() },
3361
+ { title: '適用訂單類型', html: device() },
3362
+ ],
3363
+ [
3364
+ { title: '折扣設定', html: method() },
3365
+ { title: '使用條件', html: rule() },
3366
+ ],
3367
+ [
3368
+ { title: '活動對象', html: target() },
3369
+ { title: '優惠套用至', html: setVoucherFor() },
3370
+ reBackProductView[voucherData.reBackType],
3371
+ { title: '可使用物流', html: selectShipment() },
3372
+ ],
3373
+ [
3374
+ { title: '計算單位', html: conditionType() },
3375
+ { title: '打折範圍', html: offStart() },
3376
+ { title: '重複觸發', html: countingBoolean() },
3377
+ { title: '是否與其他優惠券疊加使用', html: overlay() },
3378
+ { title: '消費金額於其他折扣觸發時機', html: isIncludeDiscount() },
3379
+ ],
3380
+ [
3381
+ { title: '全館總使用次數', html: storeUseTimeLimit() },
3382
+ { title: '個人總使用次數', html: memberUseTimeLimit() },
3383
+ { title: '購物金有效天數', html: rebateEndDay() },
3384
+ { title: '優惠卷有效日期', html: startDateTime() },
3385
+ ],
3386
+ ];
3387
+
3388
+ return BgWidget.container(
3389
+ [
3390
+ // 上層導覽
3391
+ html` <div class="title-container">
3392
+ ${[
3393
+ BgWidget.goBack(
3394
+ gvc.event(() => {
3395
+ vm.type = 'list';
3396
+ })
3397
+ ),
3398
+ BgWidget.title(
3399
+ `${obj.type === 'add' ? '新增' : '編輯'}${ShoppingDiscountSetting.getLabel(obj.reBackType)}`
3400
+ ),
3401
+ ].join('')}
3402
+ </div>`,
3403
+ // 左右容器
3404
+ BgWidget.container1x2(
3405
+ {
3406
+ html: viewList
3407
+ .map(viewData => {
3408
+ return voucherSettingCard(
3409
+ viewData.map(view => {
3410
+ if (!view.html) {
3411
+ return [];
3412
+ } else if (Array.isArray(view.html)) {
3413
+ return [setTitle(view.title), ...view.html];
3414
+ } else {
3415
+ return [setTitle(view.title), view.html];
3416
+ }
3417
+ })
3418
+ );
3419
+ })
3420
+ .join(BgWidget.mbContainer(24)),
3421
+ ratio: 68,
3422
+ },
3423
+ {
3424
+ // 摘要預覽
3425
+ html: gvc.bindView({
3426
+ bind: getUUID(),
3427
+ dataList: Object.keys(voucherData).map(key => ({ obj: voucherData, key })),
3428
+ view: () => {
3429
+ const getSummary = this.summaryTextList(voucherData)
3430
+ .map(text => {
3431
+ const className = text.length > 0 ? 'tx_normal' : 'gray-top-bottom-line-6';
3432
+ return html`<div class="${className}">${text}</div>`;
3433
+ })
3434
+ .join('');
3435
+
3436
+ return BgWidget.mainCard(
3437
+ [
3438
+ setTitle('摘要'),
3439
+ BgWidget.mbContainer(18),
3440
+ html`<div style="display: flex; gap: 12px; flex-direction: column;">${getSummary}</div>`,
3441
+ ].join('')
3442
+ );
3443
+ },
3444
+ divCreate: {
3445
+ class: 'summary-card p-0',
3446
+ },
3447
+ }),
3448
+ ratio: 32,
3449
+ }
3450
+ ),
3451
+ // 空白容器
3452
+ BgWidget.mbContainer(240),
3453
+ // 儲存資料
3454
+ html` <div class="update-bar-container">
3455
+ ${obj.type === 'replace'
3456
+ ? BgWidget.cancel(
3457
+ gvc.event(() => {
3458
+ dialog.checkYesOrNot({
3459
+ text: '是否確認刪除優惠券?',
3460
+ callback: response => {
3461
+ if (response) {
3462
+ const id = voucherData.id;
3463
+ dialog.dataLoading({ visible: true });
3464
+ ApiShop.deleteVoucher({ id }).then(res => {
3465
+ dialog.dataLoading({ visible: false });
3466
+ if (res.result) {
3467
+ vm.type = 'list';
3468
+ } else {
3469
+ dialog.errorMessage({ text: '刪除失敗' });
3470
+ }
3471
+ });
3472
+ }
3473
+ },
3474
+ });
3475
+ }),
3476
+ '刪除優惠券'
3477
+ )
3478
+ : ''}
3479
+ ${BgWidget.cancel(
3480
+ gvc.event(() => {
3481
+ vm.type = 'list';
3482
+ })
3483
+ )}
3484
+ ${BgWidget.save(
3485
+ gvc.event(() => {
3486
+ voucherData.start_ISO_Date = '';
3487
+ voucherData.end_ISO_Date = '';
3488
+
3489
+ glitter.ut.tryMethod([
3490
+ () => {
3491
+ voucherData.start_ISO_Date = new Date(
3492
+ `${voucherData.startDate} ${voucherData.startTime}`
3493
+ ).toISOString();
3494
+ },
3495
+ () => {
3496
+ voucherData.end_ISO_Date = new Date(
3497
+ `${voucherData.endDate} ${voucherData.endTime}`
3498
+ ).toISOString();
3499
+ },
3500
+ ]);
3501
+
3502
+ const requestBody = {
3503
+ postData: voucherData,
3504
+ token: (window.parent as any).saasConfig.config.token,
3505
+ type: 'manager' as 'manager',
3506
+ };
3507
+
3508
+ function responseEvent(res: any) {
3509
+ dialog.dataLoading({ visible: false });
3510
+ if (res.result) {
3511
+ vm.type = 'list';
3512
+ dialog.successMessage({ text: '上傳成功' });
3513
+ } else {
3514
+ dialog.errorMessage({ text: '上傳失敗' });
3515
+ }
3516
+ }
3517
+
3518
+ if (obj.type === 'replace') {
3519
+ dialog.dataLoading({ text: '正在更新優惠券', visible: true });
3520
+ ApiShop.putVoucher(requestBody).then(res => responseEvent(res));
3521
+ } else {
3522
+ dialog.dataLoading({ text: '正在新增優惠券', visible: true });
3523
+ ApiShop.postVoucher(requestBody).then(res => responseEvent(res));
3524
+ }
3525
+ })
3526
+ )}
3527
+ </div>`,
3528
+ ].join(BgWidget.mbContainer(24))
3529
+ );
3530
+ },
3531
+ });
3532
+ }
1922
3533
  }
1923
3534
 
1924
3535
  (window as any).glitter.setModule(import.meta.url, ShoppingDiscountSetting);