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
@@ -119,7 +119,7 @@ export interface VoucherData {
119
119
  rebate_total: number;
120
120
  }
121
121
 
122
- interface seo {
122
+ interface Seo {
123
123
  title: string;
124
124
  seo: {
125
125
  domain: string;
@@ -300,7 +300,7 @@ class OrderDetail {
300
300
  type Collection = {
301
301
  title: string;
302
302
  array: Collection[];
303
- checked: boolean;
303
+ checked?: boolean;
304
304
  product_id?: number[];
305
305
  parentTitles: string[];
306
306
  subCollections: string[];
@@ -310,9 +310,24 @@ type Collection = {
310
310
  seo_image: string;
311
311
  code: string;
312
312
  language_data: {
313
- 'en-US': seo;
314
- 'zh-CN': seo;
315
- 'zh-TW': seo;
313
+ 'en-US': Seo;
314
+ 'zh-CN': Seo;
315
+ 'zh-TW': Seo;
316
+ };
317
+ hidden?: boolean;
318
+ };
319
+
320
+ type FormatCollection = {
321
+ title: string;
322
+ array: FormatCollection[];
323
+ seo_title: string;
324
+ seo_content: string;
325
+ seo_image: string;
326
+ code: string;
327
+ language_data: {
328
+ 'en-US': Seo;
329
+ 'zh-CN': Seo;
330
+ 'zh-TW': Seo;
316
331
  };
317
332
  hidden?: boolean;
318
333
  };
@@ -768,15 +783,19 @@ export class Shopping {
768
783
  if (product.content.designated_logistics.group === '' && !product.content.designated_logistics.type) {
769
784
  product.content.designated_logistics = { list: [], type: 'all' };
770
785
  }
786
+
771
787
  product.content.collection = Array.from(
772
788
  new Set(
773
- (() => {
774
- return (product.content.collection ?? []).map((dd: any) => {
775
- return dd.replace(' / ', '/').replace(' /', '/').replace('/ ', '/').replace('/', ' / ');
776
- });
777
- })()
789
+ (product.content.collection ?? []).map(
790
+ (path: string) =>
791
+ path
792
+ .split('/') // 拆分所有層級
793
+ .map(segment => segment.trim()) // 去除空白
794
+ .join(' / ') // 統一格式為「空白/空白」
795
+ )
778
796
  )
779
797
  );
798
+
780
799
  return new Promise(async resolve => {
781
800
  if (product) {
782
801
  let totalSale = 0;
@@ -1709,18 +1728,18 @@ export class Shopping {
1709
1728
  axios
1710
1729
  .request(config)
1711
1730
  .then((response: any) => {
1712
- if(response.data.returnCode === '0000'){
1731
+ if (response.data.returnCode === '0000') {
1713
1732
  resolve(true);
1714
- }else{
1715
- console.error(`line_pay_error:`,response)
1716
- console.error(`line_pay_error_config:`,config)
1733
+ } else {
1734
+ console.error(`line_pay_error:`, response);
1735
+ console.error(`line_pay_error_config:`, config);
1717
1736
  resolve(false);
1718
1737
  }
1719
1738
  })
1720
1739
  .catch((error: any) => {
1721
1740
  resolve(false);
1722
- console.error(`line_pay_error:`,error)
1723
- console.error(`line_pay_error_config:`,config)
1741
+ console.error(`line_pay_error:`, error);
1742
+ console.error(`line_pay_error_config:`, config);
1724
1743
  });
1725
1744
  });
1726
1745
  }
@@ -3836,6 +3855,7 @@ export class Shopping {
3836
3855
  sql = `${baseSelect} \`${this.app}\`.t_checkout o ${joinClause} ${whereClause} ${orderString}`;
3837
3856
  }
3838
3857
 
3858
+ console.log(`query-sql====>${sql}`)
3839
3859
  if (query.returnSearch == 'true') {
3840
3860
  const data = await db.query(
3841
3861
  `SELECT *
@@ -3912,62 +3932,64 @@ export class Shopping {
3912
3932
  await Promise.all(
3913
3933
  obMap
3914
3934
  .map(async (order: any) => {
3915
- try {
3916
- if (order.orderData.customer_info.payment_select === 'ecPay') {
3917
- order.orderData.cash_flow = await new EcPay(this.app).checkPaymentStatus(order.cart_token);
3918
- }
3919
- if (order.orderData.customer_info.payment_select === 'paynow') {
3920
- try {
3921
- order.orderData.cash_flow = (
3922
- await new PayNow(this.app, keyData['paynow']).confirmAndCaptureOrder(order.orderData.paynow_id)
3923
- ).result;
3924
- } catch (e) {}
3925
- }
3926
- if (order.orderData.user_info.shipment_refer === 'paynow') {
3927
- const pay_now = new PayNowLogistics(this.app);
3928
- order.orderData.user_info.shipment_detail = await pay_now.getOrderInfo(order.cart_token);
3929
- const status = (() => {
3930
- switch (order.orderData.user_info.shipment_detail.PayNowLogisticCode) {
3931
- case '0000':
3932
- case '7101':
3933
- case '7201':
3934
- return 'wait';
3935
- case '0101':
3936
- case '4000':
3937
- case '4019':
3938
- case '0102':
3939
- case '9411':
3940
- return 'shipping';
3941
- case '0103':
3942
- case '4033':
3943
- case '4031':
3944
- case '4032':
3945
- case '4036':
3946
- case '4040':
3947
- case '5001':
3948
- case '8100':
3949
- case '8110':
3950
- case '8120':
3951
- return 'returns';
3952
- case '5000':
3953
- return 'arrived';
3954
- case '8000':
3955
- case '8010':
3956
- case '8020':
3957
- return 'finish';
3935
+ if(obMap.length === 1){
3936
+ try {
3937
+ if (order.orderData.customer_info.payment_select === 'ecPay') {
3938
+ order.orderData.cash_flow = await new EcPay(this.app).checkPaymentStatus(order.cart_token);
3939
+ }
3940
+ if (order.orderData.customer_info.payment_select === 'paynow') {
3941
+ try {
3942
+ order.orderData.cash_flow = (
3943
+ await new PayNow(this.app, keyData['paynow']).confirmAndCaptureOrder(order.orderData.paynow_id)
3944
+ ).result;
3945
+ } catch (e) {}
3946
+ }
3947
+ if (order.orderData.user_info.shipment_refer === 'paynow') {
3948
+ const pay_now = new PayNowLogistics(this.app);
3949
+ order.orderData.user_info.shipment_detail = await pay_now.getOrderInfo(order.cart_token);
3950
+ const status = (() => {
3951
+ switch (order.orderData.user_info.shipment_detail.PayNowLogisticCode) {
3952
+ case '0000':
3953
+ case '7101':
3954
+ case '7201':
3955
+ return 'wait';
3956
+ case '0101':
3957
+ case '4000':
3958
+ case '4019':
3959
+ case '0102':
3960
+ case '9411':
3961
+ return 'shipping';
3962
+ case '0103':
3963
+ case '4033':
3964
+ case '4031':
3965
+ case '4032':
3966
+ case '4036':
3967
+ case '4040':
3968
+ case '5001':
3969
+ case '8100':
3970
+ case '8110':
3971
+ case '8120':
3972
+ return 'returns';
3973
+ case '5000':
3974
+ return 'arrived';
3975
+ case '8000':
3976
+ case '8010':
3977
+ case '8020':
3978
+ return 'finish';
3979
+ }
3980
+ })();
3981
+ //貨態更新
3982
+ if (status && order.orderData.progress !== status) {
3983
+ order.orderData.progress = status;
3984
+ await this.putOrder({
3985
+ status: undefined,
3986
+ orderData: order.orderData,
3987
+ id: order.id,
3988
+ });
3958
3989
  }
3959
- })();
3960
- //貨態更新
3961
- if (status && order.orderData.progress !== status) {
3962
- order.orderData.progress = status;
3963
- await this.putOrder({
3964
- status: undefined,
3965
- orderData: order.orderData,
3966
- id: order.id,
3967
- });
3968
3990
  }
3969
- }
3970
- } catch (e) {}
3991
+ } catch (e) {}
3992
+ }
3971
3993
  })
3972
3994
  //補上發票號碼資訊
3973
3995
  .concat(
@@ -3982,13 +4004,27 @@ export class Shopping {
3982
4004
  ).data[0];
3983
4005
  order.invoice_number = invoice && invoice.invoice_no;
3984
4006
  })
3985
- )
3986
- //補上用戶資訊
3987
- .concat(
3988
- obMap.map(async (order: any) => {
3989
- order.user_data = await new User(this.app).getUserData(order.email, 'email_or_phone');
4007
+ ).concat(
4008
+ [
4009
+ new Promise(async resolve => {
4010
+ const setEmail=await db.query(`select * from \`${this.app}\`.t_user where (email or phone) in (
4011
+ ${
4012
+ [db.escape('dmle3'),
4013
+ ...Array.from((new Set(obMap.map((order:any)=>{
4014
+ return db.escape(`${order.email}`)
4015
+ }))))].join(',')
4016
+ }
4017
+ )`,[])
4018
+ obMap.map((order: any) => {
4019
+ const find=setEmail.find((d1:any)=>{
4020
+ return (d1.email==order.email) || (d1.phone==order.email)
4021
+ })
4022
+ order.user_data = find && find.userData;
4023
+ })
4024
+ resolve(true)
3990
4025
  })
3991
- )
4026
+ ]
4027
+ )
3992
4028
  );
3993
4029
 
3994
4030
  timer.checkPoint('finish-query-all');
@@ -4282,17 +4318,16 @@ export class Shopping {
4282
4318
  }
4283
4319
  }
4284
4320
 
4285
- async resetVoucherHistory() {
4321
+ async resetVoucherHistory(order_id:string) {
4286
4322
  try {
4287
- const resetMins = 10;
4288
- const now = moment().tz('Asia/Taipei').format('YYYY-MM-DD HH:mm:ss');
4323
+
4289
4324
  await db.query(
4290
4325
  `
4291
4326
  UPDATE \`${this.app}\`.t_voucher_history
4292
4327
  SET status = 0
4293
4328
  WHERE status = 2
4294
- AND updated_at < DATE_SUB('${now}', INTERVAL ${resetMins} MINUTE);`,
4295
- []
4329
+ AND order_id = ?;`,
4330
+ [order_id]
4296
4331
  );
4297
4332
  } catch (error) {
4298
4333
  throw exception.BadRequestError('BAD_REQUEST', 'insertVoucherHistory Error:' + e, null);
@@ -4673,7 +4708,7 @@ export class Shopping {
4673
4708
  )[0] ?? {};
4674
4709
  config.value = config.value || [];
4675
4710
 
4676
- if (replace.parentTitles[0] === '(無)') {
4711
+ if (replace.parentTitles[0] === '請選擇項目') {
4677
4712
  replace.parentTitles = [];
4678
4713
  }
4679
4714
 
@@ -4779,11 +4814,14 @@ export class Shopping {
4779
4814
  const delete_id_list = (original.product_id ?? []).filter(oid => {
4780
4815
  return (replace.product_id ?? []).findIndex(rid => rid === oid) === -1;
4781
4816
  });
4817
+
4782
4818
  if (delete_id_list.length > 0) {
4783
- const products_sql = `SELECT *
4784
- FROM \`${this.app}\`.t_manager_post
4785
- WHERE id in (${delete_id_list.join(',')});`;
4786
- const delete_product_list = await db.query(products_sql, []);
4819
+ const delete_product_list = await db.query(
4820
+ ` SELECT * FROM \`${this.app}\`.t_manager_post WHERE id IN (${delete_id_list.join(',')});
4821
+ `,
4822
+ []
4823
+ );
4824
+
4787
4825
  for (const product of delete_product_list) {
4788
4826
  product.content.collection = product.content.collection.filter((str: string) => {
4789
4827
  if (original.parentTitles[0]) {
@@ -4872,6 +4910,251 @@ export class Shopping {
4872
4910
  }
4873
4911
  }
4874
4912
 
4913
+ async putCollectionV2(replace: Collection, original: Collection) {
4914
+ try {
4915
+ // 遞迴移除 original
4916
+ const removeCollection = (config: FormatCollection[], target: Collection): number => {
4917
+ const path = target.parentTitles;
4918
+ let currentLevel = config;
4919
+
4920
+ // 先遞迴找到要刪除的分類
4921
+ for (let i = 0; i < path.length; i++) {
4922
+ const found = currentLevel.find(c => c.title === path[i]);
4923
+ if (!found) {
4924
+ console.warn(`putCollectionV2 無法找到原本的父類別:${path[i]},略過刪除`);
4925
+ return -1;
4926
+ }
4927
+ currentLevel = found.array;
4928
+ }
4929
+
4930
+ const index = currentLevel.findIndex(c => c.title === target.title);
4931
+ if (index !== -1) {
4932
+ if (Array.isArray(currentLevel[index].array)) {
4933
+ const currentLevelArray = currentLevel[index].array.filter(item => {
4934
+ return replace.subCollections.includes(item.title);
4935
+ });
4936
+
4937
+ formatData.array = currentLevelArray;
4938
+ } else {
4939
+ formatData.array = [];
4940
+ }
4941
+
4942
+ currentLevel.splice(index, 1); // 刪除原始項目
4943
+ return index;
4944
+ } else {
4945
+ console.warn(`putCollectionV2 找不到原始項目:${target.title},略過刪除`);
4946
+ return -1;
4947
+ }
4948
+ };
4949
+
4950
+ function setHiddenStatus(parentCollection: FormatCollection) {
4951
+ for (const collection of parentCollection.array) {
4952
+ collection.hidden = Boolean(parentCollection.hidden || collection.hidden);
4953
+ if (collection.array.length > 0) {
4954
+ setHiddenStatus(collection);
4955
+ }
4956
+ }
4957
+ }
4958
+
4959
+ // 插入 replace 到指定位置(和前面邏輯相同)
4960
+ function insertIntoConfig(config: FormatCollection[], replace: Collection, originIndex: number) {
4961
+ if (replace.parentTitles.length === 0 || replace.parentTitles[0] === '無') {
4962
+ const start = originIndex > -1 ? originIndex : config.length;
4963
+ config.splice(start, 0, formatData);
4964
+ return;
4965
+ }
4966
+
4967
+ const copyParentTitles = replace.parentTitles.slice();
4968
+ for (let i = 0; i < copyParentTitles.length; i++) {
4969
+ if (copyParentTitles[i] === '請選擇項目') {
4970
+ replace.parentTitles = copyParentTitles.slice(0, i);
4971
+ }
4972
+ }
4973
+
4974
+ let currentLevel = config;
4975
+ for (let i = 0; i < replace.parentTitles.length; i++) {
4976
+ const title = replace.parentTitles[i];
4977
+ const found = currentLevel.find(c => c.title === title);
4978
+ if (!found) {
4979
+ throw new Error(`找不到父類別:${title}`);
4980
+ }
4981
+ if (i === replace.parentTitles.length - 1) {
4982
+ const start = originIndex > -1 ? originIndex : found.array.length;
4983
+ found.array.splice(start, 0, formatData);
4984
+ } else {
4985
+ currentLevel = found.array;
4986
+ }
4987
+ }
4988
+ }
4989
+
4990
+ // product id 交集物件
4991
+ function analyzeIdLists(leftIds: number[], rightIds: number[]) {
4992
+ const leftSet = new Set(leftIds);
4993
+ const rightSet = new Set(rightIds);
4994
+
4995
+ return {
4996
+ left_only: leftIds.filter(id => !rightSet.has(id)),
4997
+ intersection: leftIds.filter(id => rightSet.has(id)),
4998
+ right_only: rightIds.filter(id => !leftSet.has(id)),
4999
+ all_set: [...new Set([...leftSet, ...rightSet])],
5000
+ };
5001
+ }
5002
+
5003
+ // 更新前與更新後的類別字串
5004
+ function buildStringMapFromConfig(
5005
+ config: FormatCollection[],
5006
+ original: Collection,
5007
+ replace: Collection
5008
+ ): { old: string; new: string }[] {
5009
+ const targetDepth = (original.parentTitles ?? []).length;
5010
+ const stringMap: { old: string; new: string }[] = [];
5011
+
5012
+ const originalTitle = original.title;
5013
+ const replaceTitle = replace.title;
5014
+ const replaceParentPath = [...replace.parentTitles, replace.title];
5015
+
5016
+ const findAllPaths = (item: FormatCollection, parentPath: string[] = []) => {
5017
+ const currentPath = [...parentPath, item.title];
5018
+ const fullPath = currentPath.join(' / ');
5019
+
5020
+ if (currentPath[targetDepth] === originalTitle) {
5021
+ // 只替換指定深度層級的 title
5022
+ const newPathParts = [...currentPath];
5023
+ newPathParts[targetDepth] = replaceTitle;
5024
+ let newFullPath = newPathParts.join(' / ');
5025
+
5026
+ if (fullPath.startsWith(replaceParentPath.join(' / '))) {
5027
+ // 找出在舊的商品類別中,被移除的子類別字串
5028
+ const isRemoveCol = replace.subCollections.some(sub => {
5029
+ return !fullPath.startsWith([...replaceParentPath, sub].join(' / '));
5030
+ });
5031
+ if (isRemoveCol) {
5032
+ newFullPath = '';
5033
+ }
5034
+ }
5035
+
5036
+ stringMap.push({
5037
+ old: fullPath,
5038
+ new: newFullPath,
5039
+ });
5040
+ }
5041
+
5042
+ // 遞迴處理子項
5043
+ (item.array ?? []).forEach(child => {
5044
+ findAllPaths(child, currentPath);
5045
+ });
5046
+ };
5047
+
5048
+ for (const item of config) {
5049
+ findAllPaths(item);
5050
+ }
5051
+
5052
+ return stringMap;
5053
+ }
5054
+
5055
+ // 主程式
5056
+ function main(config: FormatCollection[], original: Collection, replace: Collection): FormatCollection[] {
5057
+ const originIndex = removeCollection(config, original); // 從 config 中刪除 original
5058
+ insertIntoConfig(config, replace, originIndex); // 插入 replace 到正確位置
5059
+ for (const col of config) {
5060
+ setHiddenStatus(col);
5061
+ }
5062
+ return config;
5063
+ }
5064
+
5065
+ // 取得類別設定資料
5066
+ const configData = (
5067
+ await db.query(
5068
+ `SELECT * FROM \`${this.app}\`.public_config WHERE \`key\` = 'collection';
5069
+ `,
5070
+ []
5071
+ )
5072
+ )[0];
5073
+ const config: FormatCollection[] = configData.value || [];
5074
+
5075
+ // 排除類別標題前後空白
5076
+ replace.title = replace.title.replace(/[\s,\/\\]+/g, '');
5077
+
5078
+ // 格式化類別資料
5079
+ const formatData: FormatCollection = {
5080
+ array: [],
5081
+ code: replace.code,
5082
+ title: replace.title,
5083
+ seo_title: replace.seo_title,
5084
+ seo_content: replace.seo_content,
5085
+ seo_image: replace.seo_image,
5086
+ language_data: replace.language_data,
5087
+ hidden: Boolean(replace.hidden),
5088
+ };
5089
+
5090
+ const id_container = analyzeIdLists(original.product_id ?? [], replace.product_id ?? []);
5091
+ const update_path_array = buildStringMapFromConfig(config, original, replace);
5092
+
5093
+ // 更新類別設定資料
5094
+ main(config, original, replace);
5095
+
5096
+ // 更新商品內的類別陣列
5097
+ if (id_container.all_set.length > 0) {
5098
+ const product_list = await db.query(
5099
+ `SELECT * FROM \`${this.app}\`.t_manager_post WHERE id IN (${id_container.all_set.join(',')});`,
5100
+ []
5101
+ );
5102
+
5103
+ for (const product of product_list) {
5104
+ const currentPaths = product.content.collection ?? [];
5105
+
5106
+ const isLeftOnly = id_container.left_only.includes(product.id);
5107
+ const isRightOnly = id_container.right_only.includes(product.id);
5108
+ const isIntersection = id_container.intersection.includes(product.id);
5109
+
5110
+ // 1. 移除:left_only
5111
+ let pathsAfterRemoval = currentPaths;
5112
+ if (isLeftOnly) {
5113
+ for (const { old } of update_path_array) {
5114
+ pathsAfterRemoval = pathsAfterRemoval.filter((path: string) => path !== old);
5115
+ }
5116
+ }
5117
+
5118
+ // 2. 更新:intersection
5119
+ let pathsAfterUpdate = pathsAfterRemoval;
5120
+ if (isIntersection) {
5121
+ for (const { old, new: newVal } of update_path_array) {
5122
+ const index = pathsAfterUpdate.findIndex((p: string) => p === old);
5123
+ if (index !== -1) {
5124
+ pathsAfterUpdate[index] = newVal;
5125
+ }
5126
+ }
5127
+ }
5128
+
5129
+ // 3. 新增:right_only
5130
+ const newPath = replace.title ? [...(replace.parentTitles ?? []), replace.title] : [];
5131
+ const newMap = newPath.map((path, i) => [...newPath.slice(0, i), path].join(' / '));
5132
+
5133
+ let finalPaths = pathsAfterUpdate;
5134
+ if (isRightOnly || isIntersection) {
5135
+ finalPaths = [...new Set([...pathsAfterUpdate, ...newMap])];
5136
+ }
5137
+
5138
+ product.content.collection = finalPaths.filter((path: string) => path.length > 0);
5139
+
5140
+ await this.updateProductCollection(product.content, product.id);
5141
+ }
5142
+ }
5143
+
5144
+ // 更新商品類別 config
5145
+ await db.execute(
5146
+ `UPDATE \`${this.app}\`.public_config SET value = ? WHERE \`key\` = 'collection';
5147
+ `,
5148
+ [config]
5149
+ );
5150
+
5151
+ return { result: true };
5152
+ } catch (e) {
5153
+ console.error(e);
5154
+ throw exception.BadRequestError('BAD_REQUEST', 'putCollection Error:' + e, null);
5155
+ }
5156
+ }
5157
+
4875
5158
  async sortCollection(data: Collection[]) {
4876
5159
  try {
4877
5160
  if (data && data[0]) {
@@ -4879,9 +5162,8 @@ export class Shopping {
4879
5162
  const config =
4880
5163
  (
4881
5164
  await db.query(
4882
- `SELECT *
4883
- FROM \`${this.app}\`.public_config
4884
- WHERE \`key\` = 'collection';`,
5165
+ `SELECT * FROM \`${this.app}\`.public_config WHERE \`key\` = 'collection';
5166
+ `,
4885
5167
  []
4886
5168
  )
4887
5169
  )[0] ?? {};
@@ -4905,9 +5187,7 @@ export class Shopping {
4905
5187
  }
4906
5188
 
4907
5189
  await db.execute(
4908
- `UPDATE \`${this.app}\`.public_config
4909
- SET value = ?
4910
- WHERE \`key\` = 'collection';
5190
+ `UPDATE \`${this.app}\`.public_config SET value = ? WHERE \`key\` = 'collection';
4911
5191
  `,
4912
5192
  [config.value]
4913
5193
  );
@@ -4920,6 +5200,48 @@ export class Shopping {
4920
5200
  }
4921
5201
  }
4922
5202
 
5203
+ async sortCollectionV2(dataArray: FormatCollection[]) {
5204
+ try {
5205
+ if (!Array.isArray(dataArray) || !dataArray.length) return false;
5206
+
5207
+ const configRow = await db.query(`SELECT * FROM \`${this.app}\`.public_config WHERE \`key\` = 'collection';`, []);
5208
+ const originConfig = configRow[0]?.value ?? [];
5209
+
5210
+ function isSameStructure(origin: FormatCollection[], target: FormatCollection[]): boolean {
5211
+ const getMapByTitle = (list: FormatCollection[]) => new Map(list.map(item => [item.title, item]));
5212
+
5213
+ const originMap = getMapByTitle(origin);
5214
+ const targetMap = getMapByTitle(target);
5215
+
5216
+ if (originMap.size !== targetMap.size) return false;
5217
+
5218
+ for (const [title, a] of originMap) {
5219
+ const b = targetMap.get(title);
5220
+ if (!b) return false;
5221
+
5222
+ const aChildren = Array.isArray(a.array) ? a.array : [];
5223
+ const bChildren = Array.isArray(b.array) ? b.array : [];
5224
+
5225
+ if (!isSameStructure(aChildren, bChildren)) return false;
5226
+ }
5227
+
5228
+ return true;
5229
+ }
5230
+
5231
+ // 檢查結構相同,不管順序
5232
+ if (!isSameStructure(originConfig, dataArray)) {
5233
+ throw exception.BadRequestError('BAD_REQUEST', '僅允許更動排序,不可新增、刪除或修改分類結構。', null);
5234
+ }
5235
+
5236
+ await db.execute(`UPDATE \`${this.app}\`.public_config SET value = ? WHERE \`key\` = 'collection';`, [dataArray]);
5237
+
5238
+ return true;
5239
+ } catch (e) {
5240
+ console.error(e);
5241
+ throw exception.BadRequestError('BAD_REQUEST', 'sortCollection Error: ' + e, null);
5242
+ }
5243
+ }
5244
+
4923
5245
  checkVariantDataType(variants: any[]) {
4924
5246
  const propertiesToParse = ['stock', 'product_id', 'sale_price', 'compare_price', 'shipment_weight'];
4925
5247
 
@@ -5398,14 +5720,100 @@ export class Shopping {
5398
5720
  });
5399
5721
 
5400
5722
  // 更新商品類別
5401
- const update_col_sql = `UPDATE \`${this.app}\`.public_config
5402
- SET value = ?
5403
- WHERE \`key\` = 'collection';`;
5404
- await db.execute(update_col_sql, [config.value]);
5723
+ await db.execute(
5724
+ `UPDATE \`${this.app}\`.public_config SET value = ? WHERE \`key\` = 'collection';
5725
+ `,
5726
+ [config.value]
5727
+ );
5405
5728
 
5406
5729
  return { result: true };
5407
5730
  } catch (e) {
5408
- throw exception.BadRequestError('BAD_REQUEST', 'getCollectionProducts Error:' + e, null);
5731
+ throw exception.BadRequestError('BAD_REQUEST', 'deleteCollection Error:' + e, null);
5732
+ }
5733
+ }
5734
+
5735
+ async deleteCollectionV2(dataArray: Collection[]) {
5736
+ try {
5737
+ // 取得類別設定資料
5738
+ const configData = (
5739
+ await db.query(
5740
+ `SELECT * FROM \`${this.app}\`.public_config WHERE \`key\` = 'collection';
5741
+ `,
5742
+ []
5743
+ )
5744
+ )[0];
5745
+ const config: Collection[] = configData.value || [];
5746
+
5747
+ // 搜尋指定商品分類 SQL
5748
+ const containsAnyTagSQL = (tags: string[]): string => {
5749
+ const conditions = tags.map(tag => `JSON_CONTAINS(content, '["${tag}"]', '$.collection')`).join(' OR ');
5750
+ return `SELECT * FROM \`${this.app}\`.t_manager_post WHERE ${conditions}`;
5751
+ };
5752
+
5753
+ const removeCollection = async (config: FormatCollection[], target: Collection) => {
5754
+ const path = target.parentTitles ?? [];
5755
+ let currentLevel = config;
5756
+
5757
+ // 先遞迴找到要刪除的分類
5758
+ for (let i = 0; i < path.length; i++) {
5759
+ const found = currentLevel.find(c => c.title === path[i]);
5760
+ if (!found) {
5761
+ console.warn(`deleteCollectionV2 無法找到原本的父類別:${path[i]},略過刪除`);
5762
+ return;
5763
+ }
5764
+ currentLevel = found.array;
5765
+ }
5766
+
5767
+ const index = currentLevel.findIndex(c => c.title === target.title);
5768
+ if (index === -1) {
5769
+ console.warn(`deleteCollectionV2 找不到原始項目:${target.title},略過刪除`);
5770
+ return;
5771
+ }
5772
+
5773
+ const removed = currentLevel.splice(index, 1)[0]; // 移除並取得項目
5774
+ if (!removed) return;
5775
+
5776
+ // 同時刪除商品中使用此分類路徑的 collection
5777
+ const tagToDelete: string[] = [];
5778
+
5779
+ function gatherAllPaths(node: FormatCollection, currentPath: string[]) {
5780
+ const full = [...currentPath, node.title].join(' / ');
5781
+ tagToDelete.push(full);
5782
+
5783
+ if (node.array?.length > 0) {
5784
+ for (const child of node.array) {
5785
+ gatherAllPaths(child, [...currentPath, node.title]);
5786
+ }
5787
+ }
5788
+ }
5789
+
5790
+ gatherAllPaths(removed, path); // path = 父層,removed 是當前被刪的節點
5791
+
5792
+ // 更新每個使用該分類的商品
5793
+ const productList = await db.query(containsAnyTagSQL(tagToDelete), []);
5794
+
5795
+ for (const product of productList) {
5796
+ const before = product.content.collection ?? [];
5797
+ product.content.collection = before.filter((c: string) => !tagToDelete.includes(c));
5798
+
5799
+ await this.updateProductCollection(product.content, product.id);
5800
+ }
5801
+ };
5802
+
5803
+ for (const data of dataArray) {
5804
+ removeCollection(config, data);
5805
+ }
5806
+
5807
+ // 更新商品類別
5808
+ await db.execute(
5809
+ `UPDATE \`${this.app}\`.public_config SET value = ? WHERE \`key\` = 'collection';
5810
+ `,
5811
+ [config]
5812
+ );
5813
+
5814
+ return { result: true };
5815
+ } catch (e) {
5816
+ throw exception.BadRequestError('BAD_REQUEST', 'deleteCollection Error:' + e, null);
5409
5817
  }
5410
5818
  }
5411
5819
 
@@ -5471,10 +5879,11 @@ export class Shopping {
5471
5879
 
5472
5880
  async updateProductCollection(content: string[], id: number) {
5473
5881
  try {
5474
- const updateProdSQL = `UPDATE \`${this.app}\`.t_manager_post
5475
- SET content = ?
5476
- WHERE \`id\` = ?;`;
5477
- await db.execute(updateProdSQL, [content, id]);
5882
+ await db.execute(
5883
+ `UPDATE \`${this.app}\`.t_manager_post SET content = ? WHERE \`id\` = ?;
5884
+ `,
5885
+ [content, id]
5886
+ );
5478
5887
  } catch (error) {
5479
5888
  throw exception.BadRequestError('BAD_REQUEST', 'updateProductCollection Error:' + e, null);
5480
5889
  }