ts-glitter 21.4.4 → 21.4.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 (35) hide show
  1. package/lowcode/Entry.js +2 -2
  2. package/lowcode/Entry.ts +2 -2
  3. package/lowcode/backend-manager/bg-product.js +8 -6
  4. package/lowcode/backend-manager/bg-product.ts +17 -15
  5. package/lowcode/backend-manager/bg-recommend.js +16 -5
  6. package/lowcode/backend-manager/bg-recommend.ts +15 -5
  7. package/lowcode/backend-manager/bg-widget.js +160 -160
  8. package/lowcode/backend-manager/bg-widget.ts +169 -166
  9. package/lowcode/cms-plugin/POS-setting.js +75 -23
  10. package/lowcode/cms-plugin/POS-setting.ts +87 -30
  11. package/lowcode/cms-plugin/menus-setting.js +165 -37
  12. package/lowcode/cms-plugin/menus-setting.ts +144 -21
  13. package/lowcode/cms-plugin/module/order-excel.js +8 -8
  14. package/lowcode/cms-plugin/module/order-excel.ts +10 -10
  15. package/lowcode/cms-plugin/module/stock-excel.js +184 -0
  16. package/lowcode/cms-plugin/module/stock-excel.ts +205 -0
  17. package/lowcode/cms-plugin/module/vendor-excel.js +375 -0
  18. package/lowcode/cms-plugin/module/vendor-excel.ts +450 -0
  19. package/lowcode/cms-plugin/order/order-module.js +6 -4
  20. package/lowcode/cms-plugin/order/order-module.ts +16 -14
  21. package/lowcode/cms-plugin/pos-pages/models.ts +6 -2
  22. package/lowcode/cms-plugin/pos-pages/products-page.js +589 -469
  23. package/lowcode/cms-plugin/pos-pages/products-page.ts +657 -491
  24. package/lowcode/cms-plugin/shopping-product-setting.js +7 -10
  25. package/lowcode/cms-plugin/shopping-product-setting.ts +12 -10
  26. package/lowcode/cms-plugin/shopping-setting-basic.js +2 -5
  27. package/lowcode/cms-plugin/shopping-setting-basic.ts +2 -5
  28. package/lowcode/cms-plugin/stock-history.js +39 -26
  29. package/lowcode/cms-plugin/stock-history.ts +58 -49
  30. package/lowcode/cms-plugin/stock-vendors.js +18 -13
  31. package/lowcode/cms-plugin/stock-vendors.ts +31 -16
  32. package/package.json +1 -1
  33. package/src/api-public/services/schedule.js +6 -1
  34. package/src/api-public/services/schedule.js.map +1 -1
  35. package/src/api-public/services/schedule.ts +4 -1
@@ -0,0 +1,450 @@
1
+ import { GVC } from '../../glitterBundle/GVController.js';
2
+ import { ShareDialog } from '../../glitterBundle/dialog/ShareDialog.js';
3
+ import { BgWidget } from '../../backend-manager/bg-widget.js';
4
+ import { ApiUser } from '../../glitter-base/route/user.js';
5
+ import { Tool } from '../../modules/tool.js';
6
+ import { Excel } from './excel.js';
7
+ import { CheckInput } from '../../modules/checkInput.js';
8
+
9
+ const html = String.raw;
10
+
11
+ type Range = 'search' | 'checked' | 'all';
12
+
13
+ interface VendorData {
14
+ id: string;
15
+ name: string;
16
+ note: string;
17
+ address: string;
18
+ is_shop: boolean;
19
+ manager_name: string;
20
+ manager_phone: string;
21
+ }
22
+
23
+ export class VendorExcel {
24
+ // 取得新的ID
25
+ static getNewID(list: VendorData[]) {
26
+ let newId: string;
27
+ do {
28
+ newId = `vendor_${Tool.randomString(6)}`;
29
+ } while (list.some((item: VendorData) => item.id === newId));
30
+ return newId;
31
+ }
32
+
33
+ // 範例檔資料
34
+ static importExampleData = [
35
+ {
36
+ 供應商名稱: '範例供應商',
37
+ 供應商地址: '台北市信義區中山路一號',
38
+ 聯絡人姓名: '黃先生',
39
+ 電話: '0919334556',
40
+ 備註: '',
41
+ },
42
+ ];
43
+
44
+ // 匯出可選欄位
45
+ static headerColumn = {
46
+ 基本資料: ['供應商名稱', '供應商地址', '聯絡人姓名', '電話', '備註'],
47
+ };
48
+
49
+ // 選項元素
50
+ static optionsView(gvc: GVC, callback: (dataArray: string[]) => void) {
51
+ let columnList = new Set<string>();
52
+ const randomString = BgWidget.getCheckedClass(gvc);
53
+
54
+ const checkbox = (checked: boolean, name: string, toggle: () => void) => html`
55
+ <div class="form-check">
56
+ <input
57
+ class="form-check-input cursor_pointer ${randomString}"
58
+ type="checkbox"
59
+ id="${name}"
60
+ style="margin-top: 0.35rem;"
61
+ ${checked ? 'checked' : ''}
62
+ onclick="${gvc.event(toggle)}"
63
+ />
64
+ <label
65
+ class="form-check-label cursor_pointer"
66
+ for="${name}"
67
+ style="padding-top: 2px; font-size: 16px; color: #393939;"
68
+ >
69
+ ${name}
70
+ </label>
71
+ </div>
72
+ `;
73
+
74
+ const checkboxContainer = (items: Record<string, string[]>) => html`
75
+ <div class="row w-100">
76
+ ${Object.entries(items)
77
+ .map(([category, fields]) => {
78
+ const bindId = Tool.randomString(5);
79
+
80
+ return gvc.bindView({
81
+ bind: bindId,
82
+ view: () => {
83
+ const allChecked = fields.every(item => columnList.has(item));
84
+
85
+ return html`
86
+ ${checkbox(allChecked, category, () => {
87
+ if (allChecked) {
88
+ fields.forEach(item => columnList.delete(item));
89
+ } else {
90
+ fields.forEach(item => columnList.add(item));
91
+ }
92
+ callback(Array.from(columnList));
93
+ gvc.notifyDataChange(bindId);
94
+ })}
95
+ <div class="d-flex position-relative my-2">
96
+ ${BgWidget.leftLineBar()}
97
+ <div class="ms-4 w-100 flex-fill">
98
+ ${fields
99
+ .map(item =>
100
+ checkbox(columnList.has(item), item, () => {
101
+ columnList.has(item) ? columnList.delete(item) : columnList.add(item);
102
+ callback(Array.from(columnList));
103
+ gvc.notifyDataChange(bindId);
104
+ })
105
+ )
106
+ .join('')}
107
+ </div>
108
+ </div>
109
+ `;
110
+ },
111
+ divCreate: { class: 'col-12 col-md-4 mb-3' },
112
+ });
113
+ })
114
+ .join('')}
115
+ </div>
116
+ `;
117
+
118
+ return checkboxContainer(this.headerColumn);
119
+ }
120
+
121
+ // 匯出方法
122
+ static async export(gvc: GVC, apiJSON: any, column: string[]) {
123
+ const dialog = new ShareDialog(gvc.glitter);
124
+
125
+ if (column.length === 0) {
126
+ dialog.infoMessage({ text: '請至少勾選一個匯出欄位' });
127
+ return;
128
+ }
129
+
130
+ // 處理 JSON, 判斷欄位是否顯示
131
+ const formatJSON = (obj: Record<string, any>) =>
132
+ Object.fromEntries(Object.entries(obj).filter(([key]) => column.includes(key)));
133
+
134
+ // 供應商基本欄位物件
135
+ const getBasicJSON = (vendor: VendorData) => {
136
+ return formatJSON({
137
+ 供應商名稱: vendor.name,
138
+ 供應商地址: vendor.address,
139
+ 聯絡人姓名: vendor.manager_name,
140
+ 電話: vendor.manager_phone,
141
+ 備註: vendor.note,
142
+ });
143
+ };
144
+
145
+ function exportDataToExcel(dataArray: VendorData[]) {
146
+ if (dataArray.length === 0) {
147
+ dialog.errorMessage({ text: '無供應商資料可以匯出' });
148
+ return;
149
+ }
150
+
151
+ const printArray = dataArray.flatMap(vendor => {
152
+ return [getBasicJSON(vendor)];
153
+ });
154
+
155
+ Excel.downloadExcel(
156
+ gvc,
157
+ printArray,
158
+ `供應商列表_${gvc.glitter.ut.dateFormat(new Date(), 'yyyyMMddhhmmss')}.xlsx`,
159
+ '供應商列表'
160
+ );
161
+ }
162
+
163
+ // 透過 API, 取得供應商資料
164
+ async function fetchVendors() {
165
+ dialog.dataLoading({ visible: true });
166
+ try {
167
+ const vendors = await ApiUser.getPublicConfig('vendor_manager', 'manager').then((dd: any) => {
168
+ if (dd.result && dd.response.value) {
169
+ return dd.response.value?.list ?? [];
170
+ } else {
171
+ return [];
172
+ }
173
+ });
174
+ dialog.dataLoading({ visible: false });
175
+
176
+ if (vendors.length > 0) {
177
+ exportDataToExcel(vendors);
178
+ } else {
179
+ dialog.errorMessage({ text: '目前無供應商資料' });
180
+ }
181
+ } catch (error) {
182
+ dialog.dataLoading({ visible: false });
183
+ dialog.errorMessage({ text: '匯出檔案發生錯誤' });
184
+ }
185
+ }
186
+
187
+ dialog.checkYesOrNot({
188
+ text: '系統將會依條件匯出資料,確定要匯出嗎?',
189
+ callback: bool => bool && fetchVendors(),
190
+ });
191
+ }
192
+
193
+ // 匯出檔案彈出視窗
194
+ static exportDialog(gvc: GVC) {
195
+ const vm = {
196
+ select: 'all' as Range,
197
+ column: [] as string[],
198
+ };
199
+
200
+ BgWidget.settingDialog({
201
+ gvc,
202
+ title: '匯出供應商',
203
+ width: 700,
204
+ innerHTML: gvc2 => {
205
+ return html`<div class="d-flex flex-column align-items-start gap-2">
206
+ <div class="tx_700 mb-2">匯出範圍</div>
207
+ ${BgWidget.multiCheckboxContainer(
208
+ gvc2,
209
+ [{ key: 'all', name: '全部供應商' }],
210
+ [vm.select],
211
+ res => {
212
+ vm.select = res[0] as Range;
213
+ },
214
+ { single: true }
215
+ )}
216
+ <div class="tx_700 mb-2">匯出欄位</div>
217
+ ${this.optionsView(gvc2, cols => {
218
+ vm.column = cols;
219
+ })}
220
+ </div>`;
221
+ },
222
+ footer_html: gvc2 => {
223
+ return [
224
+ BgWidget.cancel(
225
+ gvc2.event(() => {
226
+ gvc2.glitter.closeDiaLog();
227
+ })
228
+ ),
229
+ BgWidget.save(
230
+ gvc2.event(() => {
231
+ this.export(gvc, {}, vm.column);
232
+ }),
233
+ '匯出'
234
+ ),
235
+ ].join('');
236
+ },
237
+ });
238
+ }
239
+
240
+ // 匯入方法
241
+ static async import(gvc: GVC, target: HTMLInputElement, callback: () => void) {
242
+ const dialog = new ShareDialog(gvc.glitter);
243
+
244
+ if (target.files?.length) {
245
+ try {
246
+ dialog.dataLoading({ visible: true, text: '上傳檔案中' });
247
+ const jsonData = await Excel.parseExcelToJson(gvc, target.files[0]);
248
+ dialog.dataLoading({ visible: false });
249
+
250
+ const vendors: VendorData[] = await ApiUser.getPublicConfig('vendor_manager', 'manager').then((dd: any) => {
251
+ if (dd.result && dd.response.value) {
252
+ return dd.response.value?.list ?? [];
253
+ } else {
254
+ return [];
255
+ }
256
+ });
257
+
258
+ for (let i = 0; i < jsonData.length; i++) {
259
+ const vendor = jsonData[i];
260
+
261
+ const vendorData = {
262
+ id: this.getNewID(vendors),
263
+ name: vendor['供應商名稱'],
264
+ address: vendor['供應商地址'],
265
+ manager_name: vendor['聯絡人姓名'] ?? '',
266
+ manager_phone: vendor['電話'],
267
+ note: vendor['備註'] ?? '',
268
+ is_shop: false,
269
+ };
270
+
271
+ // 名稱未填寫驗證
272
+ if (CheckInput.isEmpty(vendorData.name)) {
273
+ dialog.infoMessage({ text: `供應商名稱不得為空白(資料列第 ${i + 1} 筆)` });
274
+ return;
275
+ }
276
+
277
+ // 地址未填寫驗證
278
+ if (CheckInput.isEmpty(vendorData.address)) {
279
+ dialog.infoMessage({ text: `地址不得為空白(資料列第 ${i + 1} 筆)` });
280
+ return;
281
+ }
282
+
283
+ // 正則表達式來驗證台灣行動電話號碼格式
284
+ if (!CheckInput.isTaiwanPhone(vendorData.manager_phone)) {
285
+ dialog.infoMessage({ text: BgWidget.taiwanPhoneAlert() + `(資料列第 ${i + 1} 筆)` });
286
+ return;
287
+ }
288
+
289
+ jsonData[i] = vendorData;
290
+ }
291
+
292
+ const vendorMap = new Map(vendors.map((vendor: VendorData) => [vendor.name, vendor]));
293
+
294
+ jsonData.map((new_vendor: VendorData) => {
295
+ vendorMap.set(new_vendor.name, new_vendor);
296
+ });
297
+
298
+ dialog.checkYesOrNot({
299
+ text: '若有相同名稱的供應商,將會覆蓋並更新其資料,<br/>確定要匯入嗎?',
300
+ callback: bool => {
301
+ if (bool) {
302
+ dialog.dataLoading({ visible: true });
303
+
304
+ ApiUser.setPublicConfig({
305
+ key: 'vendor_manager',
306
+ value: {
307
+ list: [...vendorMap.values()],
308
+ },
309
+ user_id: 'manager',
310
+ }).then(() => {
311
+ dialog.dataLoading({ visible: false });
312
+ dialog.successMessage({ text: '匯入成功' });
313
+ callback();
314
+ });
315
+ }
316
+ },
317
+ });
318
+ } catch (error) {
319
+ console.error('Vendor Excel 解析失敗:', error);
320
+ }
321
+ }
322
+ }
323
+
324
+ // 匯入檔案彈出視窗
325
+ static importDialog(gvc: GVC, callback: () => void) {
326
+ const dialog = new ShareDialog(gvc.glitter);
327
+ const vm = {
328
+ id: 'importDialog',
329
+ fileInput: {} as HTMLInputElement,
330
+ type: '',
331
+ };
332
+
333
+ gvc.glitter.innerDialog((gvc: GVC) => {
334
+ return gvc.bindView({
335
+ bind: vm.id,
336
+ view: () => {
337
+ const viewData = {
338
+ title: '匯入供應商',
339
+ category: {
340
+ title: '匯入供應商類型',
341
+ options: [],
342
+ },
343
+ example: {
344
+ event: () => {
345
+ Excel.downloadExcel(
346
+ gvc,
347
+ VendorExcel.importExampleData,
348
+ `範例_供應商列表_${gvc.glitter.ut.dateFormat(new Date(), 'yyyyMMddhhmmss')}.xlsx`,
349
+ '範例供應商列表'
350
+ );
351
+ },
352
+ },
353
+ import: {
354
+ event: () =>
355
+ this.import(gvc, vm.fileInput, () => {
356
+ gvc.glitter.closeDiaLog();
357
+ callback();
358
+ }),
359
+ },
360
+ };
361
+
362
+ return html`
363
+ <div
364
+ class="d-flex align-items-center w-100 tx_700"
365
+ style="padding: 12px 0 12px 20px; align-items: center; border-radius: 10px 10px 0px 0px; background: #F2F2F2;"
366
+ >
367
+ ${viewData.title}
368
+ </div>
369
+ ${viewData.category.options.length > 0
370
+ ? html`<div class="d-flex flex-column align-items-start gap-2" style="padding: 20px 20px 0px;">
371
+ <div class="tx_700">${viewData.category.title}</div>
372
+ ${BgWidget.multiCheckboxContainer(
373
+ gvc,
374
+ viewData.category.options,
375
+ [vm.type],
376
+ res => {
377
+ vm.type = res[0];
378
+ },
379
+ { single: true }
380
+ )}
381
+ </div>`
382
+ : ''}
383
+ <div class="d-flex flex-column w-100 align-items-start gap-3" style="padding: 20px">
384
+ <div class="d-flex align-items-center gap-2">
385
+ <div class="tx_700">透過XLSX檔案匯入供應商</div>
386
+ ${BgWidget.blueNote('下載範例', gvc.event(viewData.example.event))}
387
+ </div>
388
+ <input
389
+ class="d-none"
390
+ type="file"
391
+ id="upload-excel"
392
+ onchange="${gvc.event((_, event) => {
393
+ vm.fileInput = event.target;
394
+ gvc.notifyDataChange(vm.id);
395
+ })}"
396
+ />
397
+ <div
398
+ class="d-flex flex-column w-100 justify-content-center align-items-center gap-3"
399
+ style="border: 1px solid #DDD; border-radius: 10px; min-height: 180px;"
400
+ >
401
+ ${(() => {
402
+ if (vm.fileInput.files && vm.fileInput.files.length > 0) {
403
+ return html`
404
+ ${BgWidget.customButton({
405
+ button: { color: 'snow', size: 'md' },
406
+ text: { name: '更換檔案' },
407
+ event: gvc.event(() => {
408
+ (document.querySelector('#upload-excel') as HTMLInputElement)!.click();
409
+ }),
410
+ })}
411
+ ${BgWidget.grayNote(vm.fileInput.files[0].name)}
412
+ `;
413
+ } else {
414
+ return BgWidget.customButton({
415
+ button: { color: 'snow', size: 'md' },
416
+ text: { name: '新增檔案' },
417
+ event: gvc.event(() => {
418
+ (document.querySelector('#upload-excel') as HTMLInputElement)!.click();
419
+ }),
420
+ });
421
+ }
422
+ })()}
423
+ </div>
424
+ </div>
425
+ <div class="d-flex justify-content-end gap-3" style="padding-right: 20px; padding-bottom: 20px;">
426
+ ${BgWidget.cancel(
427
+ gvc.event(() => {
428
+ gvc.glitter.closeDiaLog();
429
+ })
430
+ )}
431
+ ${BgWidget.save(
432
+ gvc.event(() => {
433
+ if (vm.fileInput.files && vm.fileInput.files.length > 0) {
434
+ viewData.import.event();
435
+ } else {
436
+ dialog.infoMessage({ text: '尚未上傳檔案' });
437
+ }
438
+ }),
439
+ '匯入'
440
+ )}
441
+ </div>
442
+ `;
443
+ },
444
+ divCreate: {
445
+ style: 'border-radius: 10px; background: #FFF; width: 570px; min-height: 360px; max-width: 90%;',
446
+ },
447
+ });
448
+ }, vm.id);
449
+ }
450
+ }
@@ -250,10 +250,10 @@ export class OrderModule {
250
250
  }
251
251
  static formatRecord(gvc, vm, orderID, record) {
252
252
  const orderNumbers = record.match(/{{order=(\d+[a-zA-Z]?)}}/g) || [];
253
- console.log("orderNumbers - ", orderNumbers);
253
+ console.log('orderNumbers - ', orderNumbers);
254
254
  orderNumbers.map((order) => {
255
255
  const pureOrder = order.replace(/{{order=|}}/g, '');
256
- console.log("pureOrder -- ", pureOrder);
256
+ console.log('pureOrder -- ', pureOrder);
257
257
  record = record.replace(order, BgWidget.blueNote(`#${pureOrder}`, gvc.event(() => {
258
258
  vm.data.cart_token = pureOrder;
259
259
  vm.type = 'replace';
@@ -718,11 +718,13 @@ export class OrderModule {
718
718
  }
719
719
  else {
720
720
  return [
721
- BgWidget.searchPlace(gvc2.event(e => {
721
+ html `<div class="position-sticky px-1" style="top: 0; background-color: #fff;">
722
+ ${BgWidget.searchPlace(gvc2.event(e => {
722
723
  vmt.search = e.value;
723
724
  vmt.loading = true;
724
725
  gvc2.notifyDataChange(vmt.id);
725
- }), vmt.search, '搜尋標籤', '0', '0'),
726
+ }), vmt.search, '搜尋標籤', '0', '0')}
727
+ </div>`,
726
728
  BgWidget.renderOptions(gvc2, vmt),
727
729
  ].join(BgWidget.mbContainer(18));
728
730
  }
@@ -274,10 +274,10 @@ export class OrderModule {
274
274
  // 處理訂單連結
275
275
 
276
276
  const orderNumbers = record.match(/{{order=(\d+[a-zA-Z]?)}}/g) || [];
277
- console.log("orderNumbers - ", orderNumbers);
277
+ console.log('orderNumbers - ', orderNumbers);
278
278
  orderNumbers.map((order: string) => {
279
279
  const pureOrder = order.replace(/{{order=|}}/g, '');
280
- console.log("pureOrder -- " , pureOrder );
280
+ console.log('pureOrder -- ', pureOrder);
281
281
  record = record.replace(
282
282
  order,
283
283
  BgWidget.blueNote(
@@ -426,7 +426,7 @@ export class OrderModule {
426
426
  }
427
427
 
428
428
  const storeKeys = Object.keys(dd.deduction_log || {});
429
- //todo 不能變更為負數
429
+ //todo 不能變更為負數
430
430
  if (storeKeys.length > 0) {
431
431
  let selectStore = '';
432
432
  for (const key of storeKeys) {
@@ -815,17 +815,19 @@ export class OrderModule {
815
815
  return BgWidget.spinner();
816
816
  } else {
817
817
  return [
818
- BgWidget.searchPlace(
819
- gvc2.event(e => {
820
- vmt.search = e.value;
821
- vmt.loading = true;
822
- gvc2.notifyDataChange(vmt.id);
823
- }),
824
- vmt.search,
825
- '搜尋標籤',
826
- '0',
827
- '0'
828
- ),
818
+ html`<div class="position-sticky px-1" style="top: 0; background-color: #fff;">
819
+ ${BgWidget.searchPlace(
820
+ gvc2.event(e => {
821
+ vmt.search = e.value;
822
+ vmt.loading = true;
823
+ gvc2.notifyDataChange(vmt.id);
824
+ }),
825
+ vmt.search,
826
+ '搜尋標籤',
827
+ '0',
828
+ '0'
829
+ )}
830
+ </div>`,
829
831
  BgWidget.renderOptions(gvc2, vmt),
830
832
  ].join(BgWidget.mbContainer(18));
831
833
  }
@@ -16,7 +16,7 @@ export class OrderDetail {
16
16
  spec: string[];
17
17
  count: number;
18
18
  sale_price: number;
19
- custom_price?:any;
19
+ custom_price?: any;
20
20
  sku: string;
21
21
  product_category?: string;
22
22
  }[];
@@ -131,6 +131,10 @@ export type ViewModel = {
131
131
  order: any;
132
132
  productSearch: any[];
133
133
  categorySearch: boolean;
134
- categories: any[];
134
+ categories: {
135
+ key: string;
136
+ value: string;
137
+ select?: boolean;
138
+ }[];
135
139
  loading: boolean;
136
140
  };