PageSourceSearch

https://www.rocsf.org.tw/js/utk.js?v=N8MTNuf2b0FokWPBHeJdn5ALR_PB1kkPu495yDGcL9Y

js rocsf.org.tw collected 2026-10-02 22:26:53 UTC 73,800 bytes, 1,822 lines download raw bytes

1(function (root, factory) {
2
3    "use strict";
4
5    // CommonJS module is defined
6    if (typeof module !== 'undefined' && module.exports) {
7        module.exports = factory(require('jquery'));
8    }
9    // AMD module is defined
10    else if (typeof define === "function" && define.amd) {
11        define("UTK", ["jquery"], function ($) {
12            return factory($);
13        });
14    } else {
15        // planted over the root!
16        root.UTK = factory(root.jQuery);
17    }
18}(this, function ($) {
19
20    "use strict";
21
22    /* ================================================
23     * Definition of UTKModal.
24     * ================================================ */
25    //var Modal = $.fn.modal.constructor;
26    //var UTKModal = function (element, options) {
27    //    Modal.call(this, element, options);
28    //};
29
30    //UTKModal.prototype = {
31    //    constructor: UTKModal
32    //};
33    //UTKModal.prototype = $.extend(UTKModal.prototype, $.fn.modal.Constructor.prototype);
34
35    /* ================================================
36     * Definition
37     * ================================================ */
38    var UTK = function (options) {
39        this.defaultOptions = $.extend(true, {
40            id: UTK.newGuid(),
41            //    'jquery.serializejson': '2.9.0',
42            //    'Keen': "v2.0.9"
43        }, UTK.defaultOptions);
44    };
45
46    UTK.newGuid = function () {
47        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
48            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
49            return v.toString(16);
50        });
51    };
52
53    //UTK.UTKModal = UTKModal;
54
55    /* ================================================
56     * default options
57     * ================================================ */
58    UTK.defaultOptions = function () {
59        return {
60            blockOptions: function (options) {
61                return $.extend(true, {
62                    overlayColor: '#000',
63                    opacity: 0.5,
64                    baseZ: 99999,
65                }, options);
66            },
67
68            ajaxOptions: function (options) {
69                return $.extend(true, {
70                    block: true,
71                    blockElement: '',
72                    type: 'POST',
73                    dataType: 'json',
74                    contentType: 'application/json; charset=utf-8',
75                    timeout: 120000,
76                    showError: true
77                }, options);
78            },
79
80            KTDataTableOptions: function (options) {
81                return $.extend(true, {
82                    data: {
83                        type: "remote",
84                        source: {
85                            read: {
86                                method: 'POST',
87                                timeout: 60000,
88                                map: function (raw) {
89                                    var dataSet = raw;
90                                    if (typeof raw.data !== 'undefined') {
91                                        dataSet = raw.data;
92                                    }
93                                    return dataSet;
94                                }
95                            }
96                        },
97                        saveState: {
98                            cookie: false,
99                            webstorage: false
100                        },
101                        //saveState: false,
102                        pageSize: 10,
103                        serverPaging: true,
104                        serverFiltering: true,
105                        serverSorting: true
106                    },
107                    layout: {
108                        theme: 'default',
109                        class: 'datatable datatable-umall datatable-bordered',
110                        scroll: true,
111                        footer: false
112                    },
113                    rows: {
114                        authHide: true
115                    },
116                    sortable: false,
117                    pagination: true,
118                    tooltip: true,
119                    toolbar: {
120                        items: {
121                            pagination: {
122                                pageSizeSelect: [5, 10, 20, 30, 50, 100]
123                            },
124                            info: true
125                        }
126                    },
127                    error: function (ev, jqXHR, msg) {
128                        swal.fire('Error', msg, 'error');
129                    },
130                    scrollto: true
131                }, options);
132            },
133
134            dateRangePickerOptions: function (options) {
135                return $.extend(true, {
136                    buttonClasses: 'btn btn-sm',
137                    applyClass: 'btn-primary',
138                    cancelClass: 'btn-default',
139                    alwaysShowCalendars: true,
140                    autoApply: false,
141                    autoUpdateInput: false,
142                    showDropdowns: true,
143                    opens: "right",
144                    maxYear: new Date().getFullYear() + 30,
145                    ranges: {
146                        "昨天": [moment().add(-1, 'days'), moment().add(-1, 'days')],
147                        "今天": [moment(), moment()],
148                        "過去3天": [moment().subtract(2, "days"), moment()],
149                        "過去7天": [moment().subtract(6, "days"), moment()],
150                        "本月": [moment().startOf("month"), moment().endOf("month")],
151                        "上個月": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")]
152                    },
153                    locale: {
154                        separator: " - ",
155                        format: "YYYY/MM/DD",
156                        firstDay: 1,
157                        applyLabel: GetSystemLanguageText("確定"),
158                        cancelLabel: GetSystemLanguageText("取消"),
159                        fromLabel: GetSystemLanguageText("開始日期"),
160                        toLabel: GetSystemLanguageText("結束日期"),
161                        customRangeLabel: "自訂起訖",
162                        daysOfWeek: ["日", "一", "二", "三", "四", "五", "六"],
163                        monthNames: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
164                    }
165                }, options);
166            },
167
168            datepickerOptions: function (options) {
169                return $.extend(true, {
170                    singleDatePicker: true,
171                    showDropdowns: true,
172                    minYear: 1901,
173                    maxYear: new Date().getFullYear() + 10,
174                    autoApply: true,
175                    locale: {
176                        separator: " - ",
177                        format: "YYYY/MM/DD",
178                        firstDay: 1,
179                        applyLabel: GetSystemLanguageText("確定"),
180                        cancelLabel: GetSystemLanguageText("取消"),
181                        fromLabel: GetSystemLanguageText("開始日期"),
182                        toLabel: GetSystemLanguageText("結束日期"),
183                        daysOfWeek: ["日", "一", "二", "三", "四", "五", "六"],
184                        monthNames: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
185                    }
186                }, options);
187            },
188            datetimepickerOptions: function (options) {
189                return $.extend(true, {
190                    singleDatePicker: true,
191                    showDropdowns: true,
192                    minYear: 1901,
193                    maxYear: new Date().getFullYear() + 10,
194                    autoApply: true,
195                    timePicker: true,
196                    timePicker24Hour: true,
197                    locale: {
198                        separator: " - ",
199                        format: "YYYY/MM/DD HH:mm",
200                        firstDay: 1,
201                        applyLabel: GetSystemLanguageText("確定"),
202                        cancelLabel: GetSystemLanguageText("清除"),
203                        fromLabel: GetSystemLanguageText("開始日期"),
204                        toLabel: GetSystemLanguageText("結束日期"),
205                        daysOfWeek: ["日", "一", "二", "三", "四", "五", "六"],
206                        monthNames: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
207                    }
208                }, options);
209            },
210
211            selectPickerOptions: function (options) {
212                return $.extend(true, {
213                }, options);
214            },
215
216            select2Options: function (options) {
217                return $.extend(true, {
218                    allowClear: true,
219                    minimumResultsForSearch: 'Infinity',
220                    width: 'resolve'
221                }, options);
222            },
223
224            DataTableOptions: function (options) {
225                return $.extend(true, {
226                    //ajax: {
227                    //    type: 'POST'
228                    //},
229                    blockElement: "body",
230                    processing: true,
231                    serverSide: false,
232                    searching: false,
233                    //ordering: true,
234                    //autoWidth :true,
235                    orderMulti: false,
236                    scrollX: true,
237                    autoFill: false,
238                    lengthMenu: [[20, 50, 100, -1], [20, 50, 100, "All"]],
239                    language: {
240                        sProcessing: GetSystemLanguageText("讀取中") + "...",
241                        sLengthMenu: GetSystemLanguageText("sLengthMenu"),
242                        sZeroRecords: GetSystemLanguageText("查無資料"),
243                        sEmptyTable: GetSystemLanguageText("查無資料"),
244                        sInfo: GetSystemLanguageText("sInfo"),
245                        sInfoEmpty: GetSystemLanguageText("查無資料"),
246                        sLoadingRecords: GetSystemLanguageText("讀取中") + "...",
247                    }
248                }, options);
249            },
250            toastOptions: function (options) {
251                return $.extend(true, {
252                    "closeButton": false,
253                    "debug": false,
254                    "newestOnTop": false,
255                    "progressBar": false,
256                    "positionClass": "toastr-top-right",
257                    "preventDuplicates": false,
258                    "onclick": null,
259                    "showDuration": "300",
260                    "hideDuration": "1000",
261                    "timeOut": "5000",
262                    "extendedTimeOut": "1000",
263                    "showEasing": "swing",
264                    "hideEasing": "linear",
265                    "showMethod": "fadeIn",
266                    "hideMethod": "fadeOut"
267                }, options);
268            },
269            quillOptions: function (options) {
270                return $.extend(true, {
271                    modules: {
272                        toolbar: {
273                            container: [
274                                ['bold', 'italic'],
275                                //['link', 'blockquote', 'code-block', 'image'],
276                                ['link', 'image', 'file'],  // 這裡有 file 的鍵
277                                [{ 'list': 'ordered' }, { 'list': 'bullet' }],
278                                [{ 'size': ['small', false, 'large', 'huge'] }],
279                                [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
280                                [{ 'color': [] }, { 'background': [] }],
281                                [{ 'align': [] }],
282                            ],
283                            handlers: {
284                                file: function () {
285                                    console.log("自訂檔案按鈕被點擊");
286                                }
287                            }
288                        }
289                    },
290                    theme: 'snow'
291                }, options);
292            },
293        }
294    }();
295
296    /* ================================================
297     * block ui
298     * ================================================ */
299    UTK.blockPage = function (options) {
300        UTK.block("body", UTK.defaultOptions.blockOptions(options));
301    };
302
303    UTK.unblockPage = function () {
304        UTK.unblock("body");
305    };
306
307    UTK.block = function (element, options) {
308        if (element == '') { element = "body"; }
309        let target = document.querySelector(element);
310        if (typeof target == 'undefined') return;
311        var blockUI = KTBlockUI.getInstance(target);
312        if (blockUI == null) {
313            blockUI = new KTBlockUI(target);
314            blockUI.block();
315        }
316        else if (!blockUI.isBlocked()) {
317            blockUI.block();
318        }
319
320        //let opt = UTK.defaultOptions.blockOptions(options);
321        //var blockUI = new KTBlockUI(target);//, opt);
322        //blockUI.block();
323
324        return blockUI;
325    };
326
327    UTK.unblock = function (element, options) {
328        if (element == '') { element = "body"; }
329        let target = document.querySelector(element);
330        if (typeof target == 'undefined') return;
331        var blockUI = KTBlockUI.getInstance(target);
332        if (blockUI != null && blockUI.isBlocked()) {
333            blockUI.release();
334        }
335    };
336
337    /* ================================================
338     * ajax
339     * ================================================ */
340    UTK.ajax = function (url, options) {
341        var headers = { 'RequestVerificationToken': $("input[name='__RequestVerificationToken']").val() };
342        if (typeof options.headers != 'undefined') {
343            $.each(options.headers, function (name, value) {
344                headers[name] = value;
345            });
346        }
347
348        var opt = $.extend({}, options, UTK.defaultOptions.ajaxOptions(options));
349
350        var ajaxParams = {
351            headers: headers,
352            data: {},
353            beforeSend: function (jqXHR) {
354                if (opt.block) {
355                    UTK.block(opt.blockElement);
356                }
357            },
358            complete: function (XMLHttpRequest, textStatus) {
359                if (opt.block) {
360                    UTK.unblock(opt.blockElement);
361                }
362            },
363            error: function (jqXHR, textStatus, errorThrown) {
364                try {
365                    let result = JSON.parse(jqXHR.responseText);
366                    console.log(result);
367                    if (options.showError) {
368                        if (jqXHR.status === 401) {
369                            // 401 身份驗證失敗,重定向到登入頁面
370                            UTK.Notice(GetSystemLanguageText("身分超時,請重新登入"), function () { window.location.href = '/account/login'; });
371
372                            return false;
373                        }
374                        Swal.fire(result._returnMessage, "Code=" + result._returnCode, "error");
375                    }
376
377                } catch {
378                    console.log(jqXHR);
379                    if (options.showError) {
380                        if (jqXHR.status === 401) {
381                            // 401 身份驗證失敗,重定向到登入頁面
382                            UTK.Notice(GetSystemLanguageText("身分超時,請重新登入"), function () { window.location.href = '/account/login'; });
383                            return false;
384                        }
385                        Swal.fire(jqXHR.status + " - " + jqXHR.statusText, "", "error");
386                    }
387                }
388            }
389        };
390
391        ajaxParams = $.extend({}, ajaxParams, opt);
392
393        return $.ajax(url, ajaxParams);
394    };
395
396    UTK.loadHtml = function (url, options) {
397        var headers = { 'RequestVerificationToken': $("input[name='__RequestVerificationToken']").val() };
398        if (typeof options.headers != 'undefined') {
399            $.each(options.headers, function (name, value) {
400                headers[name] = value;
401            });
402        }
403
404        var opt = $.extend({}, options, UTK.defaultOptions.ajaxOptions(options));
405        opt.dataType = 'html';
406
407        var ajaxParams = {
408            headers: headers,
409            data: {},
410            beforeSend: function (jqXHR) {
411                if (opt.block) {
412                    UTK.block(opt.blockElement);
413                }
414            },
415            complete: function (XMLHttpRequest, textStatus) {
416                if (opt.block) {
417                    UTK.unblock(opt.blockElement);
418                }
419            },
420            success: function (html) {
421                if (typeof opt.bindElement === 'string' && opt.bindElement != '') {
422                    $(opt.bindElement).html(html);
423                    if (typeof opt.scrollto !== "undefined" && opt.scrollto == true) {
424                        var x = $(opt.bindElement).offset().top - 100;
425                        $('html,body').animate({ scrollTop: x }, 400);
426                    }
427                }
428            },
429            error: function (jqXHR, textStatus, errorThrown) {
430                try {
431                    let result = JSON.parse(jqXHR.responseText);
432                    console.log(result);
433                    if (options.showError) {
434                        Swal.fire(result._returnMessage, "Code=" + result._returnCode, "error");
435                    }
436
437                } catch {
438                    console.log(jqXHR);
439                    if (options.showError) {
440                        Swal.fire(jqXHR.status + " - " + jqXHR.statusText, "", "error");
441                    }
442                }
443            }
444        };
445
446        ajaxParams = $.extend({}, ajaxParams, opt);
447
448        return $.ajax(url, ajaxParams);
449    };
450
451    /* ================================================
452    * form
453    * ================================================ */
454    UTK.setUIControl = function (container, options) {
455        //datepicker
456        $(container).find('[data-control="datepicker"]').each(function (index) {
457            let option = UTK.defaultOptions.datepickerOptions({});
458            let f = $(this).data('format');
459            let d = $(this).data('drops');
460            let s = $(this).data('mindate');
461
462            if (f != 'undefined') {
463                option = $.extend(true, {
464                    locale: {
465                        format: f
466                    }
467                }, option);
468            }
469            if (d != 'undefined') {
470                option = $.extend(true, {
471                    drops: d
472                }, option);
473            }
474            if (s != 'undefined') {
475                option = $.extend(true, {
476                    minDate: s
477                }, option);
478            }
479
480            $(this).daterangepicker(option);
481            $(this).attr('autocomplete', 'off');
482        });
483        //daterangepicker
484        $(container).find('[data-control="daterangepicker"]').each(function (index) {
485            let option = UTK.defaultOptions.dateRangePickerOptions({});
486            let days = $(this).data('days');
487            if (typeof days != 'undefined') {
488                option = $.extend(true, {
489                    "maxSpan": {
490                        "days": days
491                    }
492                }, option);
493            }
494            let defaultRanges = $(this).data('defaultranges');
495            if (typeof defaultRanges != 'undefined') {
496                if (defaultRanges == 'all') {
497                    option = $.extend(true, {
498                        "ranges": {
499                            'Today': [moment(), moment()],
500                            'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
501                            'Last 3 Days': [moment().subtract(2, 'days'), moment()],
502                            'Last 7 Days': [moment().subtract(6, 'days'), moment()],
503                            'Last 30 Days': [moment().subtract(29, 'days'), moment()],
504                            'This Month': [moment().startOf('month'), moment().endOf('month')],
505                            'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
506                        }
507                    }, option);
508                }
509            }
510
511            let setOptions = $(this).data('setoptions');
512            if (typeof setOptions != 'undefined' && setOptions != '') {
513                var fn = window[setOptions];
514                if (typeof fn === "function") {
515                    option = fn(option);
516                }
517            }
518
519            let container1 = $(this).data('container');
520            if (typeof container1 != 'undefined' && container1 != '') {
521                option = $.extend(true, {
522                    "parentEl": container1
523                }, option);
524            }
525            let maxYearOffset = $(this).data('max-year-offset');
526            if (typeof maxYearOffset != 'undefined') {
527                option = $.extend(true, {
528                    maxYear: new Date().getFullYear() + maxYearOffset,
529                }, option);
530            }
531            let startDate = $($(this).data('startdate-for')).val();
532            if (typeof startDate != 'undefined' && startDate != '') {
533                option = $.extend(true, {
534                    "startDate": new Date(startDate)
535                }, option);
536            }
537            let endDate = $($(this).data('enddate-for')).val();
538            if (typeof endDate != 'undefined' && endDate != '') {
539                option = $.extend(true, {
540                    "endDate": new Date(endDate)
541                }, option);
542            }
543
544            let drp = $(this).daterangepicker(option);
545            $(this).on("apply.daterangepicker", function (ev, picker) {
546                debugger;
547                UTK.daterangepickerSetDate(container, this, picker.startDate, picker.endDate);
548            });
549            $(this).on("cancel.daterangepicker", function (ev, picker) {
550                //UTK.daterangepickerSetDate(container, this, '', '');
551            });
552            $(this).on("hide.daterangepicker", function (ev, picker) {
553                $(this).data('daterangepicker').element.trigger('change');
554            });
555            $(this).on("change", function (ev) {
556                if ($(this).val() == "") {
557                    UTK.daterangepickerSetDate(container, this, '', '');
558                }
559                else {
560                    UTK.daterangepickerSetDate(container, this, $(this).data('daterangepicker').startDate, $(this).data('daterangepicker').endDate);
561                }
562            });
563            $(this).trigger('change');
564        });
565        //selectpicker
566        $(container).find('[data-control="selectpicker"]').each(function (index) {
567            let option = UTK.defaultOptions.select2Options({});
568            let s = $(this).data('allowclear');
569            if (typeof s != 'undefined') {
570                option.allowClear = false;
571            }
572            let a = $(this).data('search');
573            if (typeof a != 'undefined') {
574                option.minimumResultsForSearch = 1;
575                option.dropdownParent = $(this).parent();
576            }
577            let w = $(this).data('width');
578            if (typeof w != 'undefined') { option.width = w; }
579            let p = $(this).data('placeholder');
580            if (typeof p != 'undefined') { option.placeholder = p; }
581
582            $(this).select2(option);
583        });
584        //datepicker
585        $(container).find('[data-control="datetimepicker"]').each(function (index) {
586            let option = UTK.defaultOptions.datetimepickerOptions({});
587            let f = $(this).data('format');
588            let d = $(this).data('drops');
589            let s = $(this).data('mindate');
590            let a = $(this).data('auto')
591
592            if (typeof f != 'undefined') {
593                option = $.extend(true, {
594                    locale: {
595                        format: f
596                    }
597                }, option);
598            }
599            if (typeof d != 'undefined') {
600                option = $.extend(true, {
601                    drops: d
602                }, option);
603            }
604            if (typeof s != 'undefined') {
605                option = $.extend(true, {
606                    minDate: s
607                }, option);
608            }
609            if (typeof a != 'undefined') {
610                option = $.extend(true, {
611                    autoApply: false
612                }, option);
613            }
614
615            $(this).daterangepicker(option);
616            $(this).on('cancel.daterangepicker', function (ev, picker) {
617                $(this).val('');
618            });
619            $(this).attr('autocomplete', 'off');
620        });
621
622        //TODO: timepicker
623        $(container).find('[data-control="timepicker"]').each(function (index) { });
624
625        $(container).find('[data-control="countup"]').each(function (item, index) {
626            var ele = new CountUp(index, $(this).data('countup-value'));
627            ele.start();
628        });
629
630        $(container).find('[data-control="htmleditor"]').each(function (index) {
631            var icons = Quill.import('ui/icons');
632            // 設定 file 鍵的圖示(這裡使用 Font Awesome 的 file icon)
633            icons['file'] = '<i class="fa-solid fa-upload"></i>';
634            var editor = new Quill(this,
635                UTK.defaultOptions.quillOptions({
636                    placeholder: $(this).prop('placeholder')
637                }));
638            $(this).find('[class=ql-editor]').attr('id', this.id + '_ql_editor');
639
640            //更換Html編輯器的上傳檔案功能
641            //會呼叫 homecontroller中的imageupload方法
642            editor.getModule('toolbar').addHandler('image', function () {
643                const input = document.createElement('input');
644                input.setAttribute('type', 'file');
645                input.setAttribute('accept', 'image/png, image/jpeg')
646                input.click();
647
648                // Listen upload local image and save to server
649                input.onchange = function () {
650                    const file = input.files[0];
651                    const fd = new FormData();
652                    fd.append('files', file);
653                    //限制檔案大小
654
655                    const xhr = new XMLHttpRequest();
656                    xhr.open('POST', '/quillimageupload', true);
657                    xhr.setRequestHeader('RequestVerificationToken', $("input[name='__RequestVerificationToken']").val());
658                    xhr.onload = function () {
659                        if (xhr.status === 200) {
660                            // this is callback data: url
661                            const url = JSON.parse(xhr.responseText).Data;
662                            const range = editor.getSelection();
663                            editor.insertEmbed(range.index, 'image', `/getshareimage/quillhtmledit/${url.toString()}`);
664                        }
665                    };
666                    xhr.send(fd);
667                };
668            });
669            //檔案上傳 可上傳PDF,Word,Excel等檔案,上傳後會得到轉換後的網址
670            editor.getModule('toolbar').addHandler('file', function () {
671                const input = document.createElement('input');
672                input.setAttribute('type', 'file');
673                input.setAttribute('accept', 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.text');
674                input.click();
675
676                // Listen upload local image and save to server
677                input.onchange = function () {
678                    debugger;
679                    const file = input.files[0];
680                    const fd = new FormData();
681                    fd.append('files', file);
682                    //限制檔案大小
683
684                    const xhr = new XMLHttpRequest();
685                    xhr.open('POST', '/quillfileupload', true);
686                    xhr.setRequestHeader('RequestVerificationToken', $("input[name='__RequestVerificationToken']").val());
687                    xhr.onload = function () {
688                        if (xhr.status === 200) {
689                            /// 取得回傳資料中的 URL
690                            const url = JSON.parse(xhr.responseText).Data;
691                            // 若沒有選擇範圍,預設插入到編輯器最後
692                            const range = editor.getSelection() || { index: editor.getLength() };
693                            const filename = file.name;
694                            const newUrl = `/getsharefile/quillhtmledit/${url.toString()}`;
695
696                            // 使用 insertText,並設定格式為連結
697                            editor.insertText(range.index, filename, { link: newUrl });
698                            // 更新游標位置
699                            editor.setSelection(range.index + filename.length);
700                        }
701                    };
702                    xhr.send(fd);
703                };
704            });
705        });
706
707        //移除瀏覽器自動填入
708        $(container).find('input[type="text"]').attr('autocomplete', 'off');
709        $(container).find('input[type="email"], input[type="password"]').attr('autocomplete', 'nope');
710        $(container).on('keyup keypress', function (e) {
711            var keyCode = e.keyCode || e.which;
712            if (keyCode === 13 && e.target.tagName !== 'TEXTAREA') {
713                e.preventDefault();
714                return false;
715            }
716        });
717        //給予畫面元素 maxlength效果
718        $(container).find('input[type="text"],input[type="number"],textarea').each(function () {
719            if ($(this).attr('maxlength') !== undefined) {
720                $(this).maxlength({
721                    warningClass: "badge badge-primary",
722                    limitReachedClass: "badge badge-success"
723                });
724            }
725        });
726
727        //因為效能問題 Tooltip必須自行初始化
728        $(container).find('[data-bs-toggle="tooltip"]').each(function () {
729            new bootstrap.Tooltip($(this));
730        });
731
732        //會儲存按鈕加上Loading效果,一秒後恢復正常按鈕
733        $(container).find('[data-control="savebutton"]').each(function () {
734            $(this).on('click', function () {
735                var thisButton = $(this);
736                thisButton.attr('disabled', true);
737                setTimeout(function () { thisButton.removeAttr('disabled'); }, 1000);
738            });
739        });
740
741        moment.locale('zh-tw');
742        $(container).find('[data-moment]').each(function () {
743            var dtime = $(this).data('moment');
744            if (dtime) {
745                $(this).text(moment(dtime).fromNow());
746                $(this).val(moment(dtime).fromNow());
747            }
748        })
749
750    };
751
752    UTK.setQueryUIControl = function (container, defaultOrderBy, query) {
753        if (defaultOrderBy) {
754            var x = 0;
755            $(container).find('.tableOrderBy').each(function () {
756                var obj = $(this);
757                var foundItem = defaultOrderBy.find(x => x.Column == obj.data('orderby'));
758
759                if (foundItem && foundItem.Dir == 'ASC') {
760                    $(this).append('<i class="icon asc fa-solid fa-caret-up ms-2 text-white"></i>');
761                    obj.data('sort', x++);
762                } else if (foundItem && foundItem.Dir == 'DESC') {
763                    $(this).append('<i class="icon desc fa-solid fa-caret-down ms-2 text-white"></i>');
764                    obj.data('sort', x++);
765                }
766            });
767        }
768        $(container).find('.tableOrderBy').click(function () {
769            //若沒有.icon 就加上<i class="icon asc fa-solid fa-caret-up"></i> 和加上 sort
770            //若有.icon asc 就加上 <i class="icon desc fa-solid fa-caret-down"></i>
771            //若有.icon desc 就移除 .icon & sort
772            var obj = $(this);
773            if (obj.find('.icon').length == 0) {
774                var maxSort = 0; //找icon中最大的sort
775                $('.tableOrderBy .icon').each(function () {
776                    var sort = parseInt($(this).parent().data('sort'));
777                    if (!isNaN(sort) && sort > maxSort) {
778                        maxSort = sort;
779                    }
780                });
781                obj.append('<i class="icon asc fa-solid fa-caret-up ms-2 text-white"></i>');
782                obj.data('sort', maxSort + 1);
783            }
784            else if (obj.find('.icon.asc').length > 0) {
785                obj.find('.icon').remove();
786                obj.append('<i class="icon desc fa-solid fa-caret-down ms-2 text-white"></i>');
787            }
788            else {
789                obj.find('.icon').remove();
790                obj.removeData('sort');
791            }
792
793            query();
794        });
795    }
796
797    UTK.daterangepickerSetDate = function (container, element, startDate, endDate) {
798        let startDateElement = $(element).data('startdate-for');
799        let endDateElement = $(element).data('enddate-for');
800        if ((typeof startDate === 'string' && startDate == '') ||
801            (typeof endDate === 'string' && endDate == '')) {
802            $(element).val("");
803            if (typeof startDateElement !== 'undefined') {
804                $(container).find(startDateElement).val('');
805            }
806            if (typeof endDateElement !== 'undefined') {
807                $(container).find(endDateElement).val('');
808            }
809        }
810        else {
811            let drp = $(element).data('daterangepicker');
812            drp.setStartDate(startDate);
813            drp.setEndDate(endDate);
814            var startValue = drp.startDate.format(drp.locale.format);
815            var endValue = drp.endDate.format(drp.locale.format);
816            $(element).val(startValue + drp.locale.separator + endValue);
817            $(container).find(startDateElement).val(startValue);
818            $(container).find(endDateElement).val(endValue);
819        }
820    };
821
822    UTK.formReset = function (container) {
823        let forms = $(container).find('form');
824        forms.each(function (index) {
825            forms[index].reset();
826        });
827        $(container).find('[data-control-selectpicker]').each(function (index) {
828            $(this).selectpicker('refresh');
829        });
830        $(container).find('[data-control="selectpicker"]').each(function (index) {
831            //$(this).selectpicker('refresh'); //bootstrap-selectpicker用的
832            $(this).val('').trigger('change');
833        });
834
835        $(container).find('[data-control-daterangepicker]').each(function (index) {
836            $(this).trigger('change');
837        });
838        $(container).find('[data-control="daterangepicker"]').each(function (index) {
839            $(this).trigger('change');
840        });
841        $(container).find('.tableOrderBy').each(function () {
842            $(this).find('.icon').remove();
843            $(this).removeData('sort');
844        });
845    };
846
847    UTK.formValidation = function (container, options) {
848        var el = $(container).get(0);
849        var opt = $.extend(true,
850            {
851                noMessage: false,
852                plugins: {
853                    trigger: new FormValidation.plugins.Trigger(),
854                    submitButton: new FormValidation.plugins.SubmitButton(),
855                    bootstrap: new FormValidation.plugins.Bootstrap5(),
856                    //icon: new FormValidation.plugins.Icon({
857                    //    valid: 'fa fa-check',
858                    //    invalid: 'fa fa-times',
859                    //    validating: 'fa fa-refresh',
860                    //}),
861                }
862            }, options);
863
864        var validation = FormValidation.formValidation(el, opt)
865            .on('core.field.invalid', function (e) {
866                $('#' + e).focus();
867            })
868            .on('plugins.message.displayed', function (e) {
869                if (!opt.noMessage) {
870                    e.messageElement.style.display = 'none';
871                }
872            });
873
874        return validation;
875    }
876
877    UTK.formJSON = function (element) {
878        var myform = $(element);
879        var disabled = myform.find(':input:disabled').removeAttr('disabled');
880        var json = myform.serializeJSON();
881        disabled.attr('disabled', 'disabled');
882
883        return json;
884    };
885    UTK.formQueryJSON = function (queryform,element) {
886        var myform = $(queryform);
887        var disabled = myform.find(':input:disabled').removeAttr('disabled');
888        var json = myform.serializeJSON();
889        disabled.attr('disabled', 'disabled');
890
891        json.Order = [];
892        $(element).find('.tableOrderBy > .icon').each(function () {
893            var obj = $(this).parent();
894            var dir = obj.find('.icon.asc').length > 0 ? 'ASC' : 'DESC';
895            var sort = obj.data('sort');
896
897            if (json.Order.find(x => x.Column == obj.data('orderby'))) return;
898            json.Order.push({ Column: obj.data('orderby'), ColumnAllPage: obj.data('allorderby'), Dir: dir, Sort: sort });
899        });
900        json.Order = json.Order.sort((a, b) => a.Sort - b.Sort).map(x => { delete x.Sort; return x; });
901
902        return json;
903    };
904    UTK.formSetQueryJSON = function (element,sessionName) {
905        var myform = $(element);
906        var searchParams = sessionStorage.getItem(sessionName);
907        if (searchParams) {
908            searchParams = JSON.parse(searchParams);
909            myform.find('input').each(function () {                
910                var name = $(this).attr('name');
911                var type = $(this).attr('type');
912                if (searchParams[name]) {
913                    if (type == 'checkbox') {
914                        $(this).prop('checked', searchParams[name]);
915                    }
916                    else if (type == 'radio') {
917                        if ($(this).val() == searchParams[name]) {
918                            $(this).prop('checked', true);
919                        }
920                    }
921                    else $(this).val(searchParams[name]);
922                }
923            });
924            myform.find('select').each(function () {
925                var name = $(this).attr('name');
926                if (searchParams[name]) {
927                    $(this).val(searchParams[name]).trigger('change');;
928                }
929            });
930        }
931    }
932
933    UTK.getFormData = function (form) {
934        var data = new FormData($(form).get(0));
935        return data;
936    };
937
938    UTK.Notice = function (text, success) {
939        Swal.fire({
940            title: GetSystemLanguageText("警告"),
941            text: text,
942            icon: "warning",
943            buttonsStyling: false,
944            confirmButtonText: GetSystemLanguageText("關閉"),
945            customClass: {
946                confirmButton: "btn btn-primary"
947            }
948        }).then((result) => {
949            if (typeof success === 'function') success(result);
950        });
951    }
952    UTK.NoticeHtml = function (text, success) {
953        Swal.fire({
954            html: text,
955            title: GetSystemLanguageText("警告"),
956            icon: "warning",
957            buttonsStyling: false,
958            confirmButtonText: GetSystemLanguageText("關閉"),
959            customClass: {
960                confirmButton: "btn btn-primary"
961            }
962        }).then((result) => {
963            if (typeof success === 'function') success(result);
964        });
965    }
966
967    UTK.Success = function (text, success) {
968        Swal.fire({
969            title: GetSystemLanguageText("成功"),
970            text: text,
971            icon: "success",
972            buttonsStyling: false,
973            confirmButtonText: GetSystemLanguageText("關閉"),
974            customClass: {
975                confirmButton: "btn btn-primary"
976            }
977        }).then((result) => {
978            if (typeof success === 'function') success(result);
979        });
980    }
981
982    UTK.SuccessHtml = function (text, success) {
983        Swal.fire({
984            html: text,
985            title: GetSystemLanguageText("成功"),
986            icon: "success",
987            buttonsStyling: false,
988            confirmButtonText: GetSystemLanguageText("關閉"),
989            customClass: {
990                confirmButton: "btn btn-primary"
991            }
992        }).then((result) => {
993            if (typeof success === 'function') success(result);
994        });
995    }
996
997    UTK.AutoCloseNotice = function (text, success, title, time) {
998        //若time 沒給值 預設5
999        time = time || 5;
1000        // 若 title 沒給值,預設為 "警告"
1001        title = title || GetSystemLanguageText("警告");
1002
1003        Swal.fire({
1004            title: title,
1005            html: `${text}<br><br><span style="font-size:0.8rem;">彈跳視窗將在 <b id="timer">${time}</b> 秒後關閉</span>`,
1006            timer: time * 1000, // 設置自動關閉時間為5秒
1007            timerProgressBar: true, // 顯示進度條
1008            showConfirmButton: false, // 隱藏確認按鈕
1009            didOpen: () => {
1010                // 開始倒數計時
1011                const timerElement = document.getElementById('timer');
1012                let timerInterval = setInterval(() => {
1013                    let timeLeft = parseInt(timerElement.textContent, 10);
1014                    if (timeLeft > 1) {
1015                        timerElement.textContent = timeLeft - 1;
1016                    } else {
1017                        clearInterval(timerInterval);
1018                    }
1019                }, 1000); // 每秒更新一次倒數計時
1020            }
1021        }).then((result) => {
1022            if (result.dismiss === Swal.DismissReason.timer) {
1023                if (typeof success === 'function') {
1024                    success(result);
1025                }
1026            }
1027        });;
1028    }
1029
1030    UTK.SaveButton = function (success, msg) {
1031        Swal.fire({
1032            title: msg,
1033            text: GetSystemLanguageText('確定要') + msg + '?',
1034            icon: "warning",
1035            iconColor: "#009EF7",
1036            buttonsStyling: false,
1037            showCancelButton: true,
1038            confirmButtonText: GetSystemLanguageText("確定"),
1039            cancelButtonText: GetSystemLanguageText("取消"),
1040            customClass: {
1041                confirmButton: "btn btn-primary",
1042                cancelButton: "btn btn-light"
1043            }
1044        }).then((result) => {
1045            if (result.isConfirmed) {
1046                if (typeof success === 'function') {
1047                    success();
1048                }
1049            }
1050        });
1051    }
1052
1053    UTK.ServerError = function (html, success) {
1054        Swal.fire({
1055            title: '<b>' + GetSystemLanguageText("錯誤") + '</b>',
1056            html: html,
1057            icon: "error",
1058            buttonsStyling: false,
1059            confirmButtonText: GetSystemLanguageText("關閉"),
1060            customClass: {
1061                confirmButton: "btn btn-primary"
1062            }
1063        }).then((result) => {
1064            if (typeof success === 'function') success(result);
1065        });
1066    }
1067
1068    UTK.Error = function (text, success) {
1069        Swal.fire({
1070            title: GetSystemLanguageText("錯誤"),
1071            text: text,
1072            icon: "error",
1073            buttonsStyling: false,
1074            confirmButtonText: GetSystemLanguageText("關閉"),
1075            customClass: {
1076                confirmButton: "btn btn-primary"
1077            }
1078        }).then((result) => {
1079            if (typeof success === 'function') success(result);
1080        });
1081    }
1082
1083    UTK.ErrorHtml = function (text, success) {
1084        Swal.fire({
1085            html: text,
1086            title: GetSystemLanguageTex("錯誤"),
1087            icon: "error",
1088            buttonsStyling: false,
1089            confirmButtonText: GetSystemLanguageText("關閉"),
1090            customClass: {
1091                confirmButton: "btn btn-primary"
1092            }
1093        }).then((result) => {
1094            if (typeof success === 'function') success(result);
1095        });
1096    }
1097
1098    UTK.ReturnPage = function (success) {
1099        Swal.fire({
1100            title: GetSystemLanguageText("返回"),
1101            text: GetSystemLanguageText("確定離開此頁嗎?"),
1102            icon: "warning",
1103            buttonsStyling: false,
1104            showCancelButton: true,
1105            confirmButtonText: GetSystemLanguageText("確定"),
1106            cancelButtonText: GetSystemLanguageText("取消"),
1107            customClass: {
1108                confirmButton: "btn btn-primary",
1109                cancelButton: "btn btn-secondary"
1110            }
1111        }).then((result) => {
1112            if (result.isConfirmed) {
1113                if (typeof success === 'function') {
1114                    success();
1115                }
1116            }
1117        });
1118    }
1119
1120    UTK.Comfirm = function (title, text, success) {
1121        Swal.fire({
1122            title: title,
1123            text: text,
1124            icon: "info",
1125            iconColor: "#E31C3B",
1126            buttonsStyling: false,
1127            showCancelButton: true,
1128            confirmButtonText: GetSystemLanguageText("確定"),
1129            cancelButtonText: GetSystemLanguageText("取消"),
1130            customClass: {
1131                confirmButton: "btn btn-primary",
1132                cancelButton: "btn btn-light"
1133            }
1134        }).then((result) => {
1135            if (result.isConfirmed) {
1136                if (typeof success === 'function') {
1137                    success();
1138                }
1139            }
1140        });
1141    }
1142
1143    UTK.Delete = function (success) {
1144        Swal.fire({
1145            title: GetSystemLanguageText("刪除"),
1146            text: GetSystemLanguageText('確定刪除此筆資料嗎?'),
1147            icon: "error",
1148            buttonsStyling: false,
1149            showCancelButton: true,
1150            confirmButtonText: GetSystemLanguageText("確定"),
1151            cancelButtonText: GetSystemLanguageText("取消"),
1152            customClass: {
1153                confirmButton: "btn btn-primary",
1154                cancelButton: "btn btn-secondary"
1155            }
1156        }).then((result) => {
1157            if (result.isConfirmed) {
1158                if (typeof success === 'function') {
1159                    success();
1160                }
1161            }
1162        });
1163    }
1164
1165    UTK.Confirm = function (title, text, confirmFunction) {
1166        Swal.fire({
1167            title: title,
1168            text: text,
1169            icon: "question",
1170            buttonsStyling: false,
1171            showCancelButton: true,
1172            confirmButtonText: GetSystemLanguageText("確定"),
1173            cancelButtonText: GetSystemLanguageText("取消"),
1174            customClass: {
1175                confirmButton: "btn btn-primary",
1176                cancelButton: "btn btn-secondary"
1177            }
1178        }).then((result) => {
1179            if (result.isConfirmed) {
1180                if (confirmFunction) {
1181                    confirmFunction();
1182                }
1183            }
1184        });
1185    }
1186
1187    UTK.ConfirmHtml = function (title, html, success) {
1188        Swal.fire({
1189            title: title,
1190            html: html,
1191            icon: "question",
1192            buttonsStyling: false,
1193            showCancelButton: true,
1194            confirmButtonText: GetSystemLanguageText("確定"),
1195            cancelButtonText: GetSystemLanguageText("取消"),
1196            customClass: {
1197                confirmButton: "btn btn-primary",
1198                cancelButton: "btn btn-secondary"
1199            }
1200        }).then((result) => {
1201            if (result.isConfirmed) {
1202                if (typeof success === 'function') {
1203                    success();
1204                }
1205            }
1206        });
1207    }
1208
1209    UTK.Swal = function (option, success) {
1210        let options = $.extend(true, {
1211            icon: "question",
1212            buttonsStyling: false,
1213            showCancelButton: true,
1214            confirmButtonText: GetSystemLanguageText("確定"),
1215            cancelButtonText: GetSystemLanguageText("取消"),
1216            customClass: {
1217                confirmButton: "btn btn-primary",
1218                cancelButton: "btn btn-secondary"
1219            }
1220        }, option);
1221
1222        return Swal.fire(options)
1223    }
1224
1225    UTK.toastr_success = function (msg, options) {
1226        var defaultOptions = UTK.defaultOptions.toastOptions(options);
1227        toastr.options = defaultOptions;
1228        toastr.success(msg, '', {
1229            "timeOut": "2000"
1230        });
1231    };
1232
1233    UTK.toastr_warning = function (msg, options) {
1234        var defaultOptions = UTK.defaultOptions.toastOptions(options);
1235        toastr.options = defaultOptions;
1236        toastr.warning(msg, '', {
1237            "timeOut": "2000"
1238        });
1239    };
1240
1241    UTK.toastr_error = function (msg, options) {
1242        var defaultOptions = UTK.defaultOptions.toastOptions(options);
1243        toastr.options = defaultOptions;
1244        toastr.error(msg, '', {
1245            "timeOut": "3000"
1246        });
1247    };
1248
1249    /* ================================================
1250     * KTDataTable
1251     * ================================================ */
1252    UTK.KTDatatable = function (element, options) {
1253        let opt = UTK.defaultOptions.KTDataTableOptions(options);
1254        var dataTable = $(element).KTDatatable(opt);
1255
1256        $(element).on('datatable-on-ajax-done', function (ev, data) {
1257            if (typeof opt.scrollto !== "undefined" && opt.scrollto == true) {
1258                $("html, body").animate({ scrollTop: $(element).offset().top }, 0);
1259            }
1260
1261            if (typeof (data._returnCode) !== 'undefined' && typeof (data._returnMessage) !== 'undefined') {
1262                try {
1263                    if (data.Code != "0000") {
1264                        swal.fire('Error', '[' + data._returnCode + '] ' + data._returnMessage, 'error');
1265                        return;
1266                    }
1267                }
1268                catch (e) { }
1269            }
1270
1271            if ($.isFunction(opt.done)) {
1272                opt.done(ev, data);
1273            }
1274        });
1275
1276        $(element).on('datatable-on-ajax-fail', function (ev, jqXHR) {
1277            console.log(jqXHR);
1278
1279            if (typeof (jqXHR.responseJSON) !== 'undefined' &&
1280                typeof (jqXHR.responseJSON._returnCode) !== 'undefined' &&
1281                typeof (jqXHR.responseJSON._returnMessage) !== 'undefined') {
1282                try {
1283                    if (jqXHR.responseJSON.Code != "0000") {
1284                        swal.fire('Error', '[' + jqXHR.responseJSON._returnCode + '] ' + jqXHR.responseJSON._returnMessage, 'error');
1285                        return;
1286                    }
1287                }
1288                catch (e) { }
1289            }
1290
1291            if ($.isFunction(opt.error)) {
1292                var msg = jqXHR.responseJSON ? jqXHR.responseJSON._returnMessage : jqXHR.status + " (" + jqXHR.statusText + ")";
1293                opt.error(ev, jqXHR, msg);
1294            }
1295            else {
1296
1297            }
1298        });
1299
1300        $(element).on('datatable-on-reloaded', function (ev, jqXHR) {
1301        });
1302
1303        return dataTable;
1304    };
1305
1306    /* ================================================
1307     * DataTables
1308     * ================================================ */
1309
1310    /**
1311     * 安全的 DataTables Render 函式集合
1312     * 用於防止 XSS 攻擊
1313     */
1314    UTK.safeRender = {
1315        /**
1316         * 安全的文字 Render - 將所有 HTML 字元轉義
1317         * @param {string} data - 資料
1318         * @param {string} type - 類型 (display, filter, sort, type)
1319         * @param {object} row - 整列資料
1320         * @returns {string} - 轉義後的安全字串
1321         * @example columns: [{ data: 'name', render: UTK.safeRender.text }]
1322         */
1323        text: function (data, type, row) {
1324            if (type === 'display' || type === 'filter') {
1325                return UTK.escapeHtml(data == null ? '' : String(data));
1326            }
1327            return data;
1328        },
1329
1330        /**
1331         * 安全的連結 Render
1332         * @param {string} urlField - URL 欄位名稱
1333         * @param {string} textField - 顯示文字欄位名稱 (可選,預設使用 data)
1334         * @param {string} target - 開啟方式 (可選,預設 _self)
1335         * @returns {function} - Render 函式
1336         * @example columns: [{ data: 'name', render: UTK.safeRender.link('url', 'name', '_blank') }]
1337         */
1338        link: function (urlField, textField, target) {
1339            target = target || '_self';
1340            return function (data, type, row) {
1341                if (type === 'display') {
1342                    var url = UTK.escapeHtml(row[urlField] || '#');
1343                    var text = UTK.escapeHtml(textField ? (row[textField] || data) : data);
1344                    text = text == null ? '' : String(text);
1345                    return '<a href="' + url + '" target="' + target + '">
1345' + text + '</a>';
1346                }
1347                return data;
1348            };
1349        },
1350
1351        /**
1352         * 安全的 HTML Render - 只允許特定標籤
1353         * @param {Array} allowedTags - 允許的 HTML 標籤陣列
1354         * @returns {function} - Render 函式
1355         * @example columns: [{ data: 'content', render: UTK.safeRender.html(['b', 'i', 'br']) }]
1356         */
1357        html: function (allowedTags) {
1358            allowedTags = allowedTags || ['b', 'i', 'u', 'strong', 'em', 'br', 'span'];
1359            return function (data, type, row) {
1360                if (type === 'display') {
1361                    if (data == null) return '';
1362                    // 先轉義所有內容
1363                    var escaped = UTK.escapeHtml(String(data));
1364                    // 再還原允許的標籤
1365                    allowedTags.forEach(function (tag) {
1366                        var openTagRegex = new RegExp('&lt;' + tag + '(\\s[^&]*)?&gt;', 'gi');
1367                        var closeTagRegex = new RegExp('&lt;/' + tag + '&gt;', 'gi');
1368                        escaped = escaped.replace(openTagRegex, '<' + tag + '$1>');
1369                        escaped = escaped.replace(closeTagRegex, '</' + tag + '>');
1370                    });
1371                    return escaped;
1372                }
1373                return data;
1374            };
1375        },
1376
1377        /**
1378         * 安全的圖片 Render
1379         * @param {string} srcField - 圖片 URL 欄位名稱
1380         * @param {string} altField - Alt 文字欄位名稱 (可選)
1381         * @param {string} cssClass - CSS 類別 (可選)
1382         * @returns {function} - Render 函式
1383         * @example columns: [{ data: 'imageUrl', render: UTK.safeRender.image('imageUrl', 'name', 'img-thumbnail') }]
1384         */
1385        image: function (srcField, altField, cssClass) {
1386            cssClass = cssClass || '';
1387            return function (data, type, row) {
1388                if (type === 'display') {
1389                    var src = UTK.escapeHtml(row[srcField] || '');
1390                    var alt = UTK.escapeHtml(altField ? (row[altField] || '') : '');
1391                    if (!src) return '';
1392                    return '<img src="' + src + '" alt="' + alt + '" class="' + cssClass + '">';
1393                }
1394                return data;
1395            };
1396        },
1397
1398        /**
1399         * 安全的日期 Render
1400         * @param {string} format - 日期格式 (使用 moment.js)
1401         * @returns {function} - Render 函式
1402         * @example columns: [{ data: 'createAt', render: UTK.safeRender.date('YYYY/MM/DD HH:mm') }]
1403         */
1404        date: function (format) {
1405            format = format || 'YYYY/MM/DD';
1406            return function (data, type, row) {
1407                if (type === 'display' || type === 'filter') {
1408                    if (!data) return '';
1409                    return moment(data).format(format);
1410                }
1411                return data;
1412            };
1413        },
1414
1415        /**
1416         * 安全的數字 Render (加上千分位)
1417         * @param {number} decimals - 小數位數 (可選,預設 0)
1418         * @returns {function} - Render 函式
1419         * @example columns: [{ data: 'amount', render: UTK.safeRender.number(2) }]
1420         */
1421        number: function (decimals) {
1422            decimals = decimals || 0;
1423            return function (data, type, row) {
1424                if (type === 'display') {
1425                    if (data == null || data === '') return '';
1426                    var num = parseFloat(data);
1427                    if (isNaN(num)) return UTK.escapeHtml(String(data));
1428                    return num.toLocaleString('zh-TW', {
1429                        minimumFractionDigits: decimals,
1430                        maximumFractionDigits: decimals
1431                    });
1432                }
1433                return data;
1434            };
1435        },
1436
1437        /**
1438         * 不進行轉義的 Render (僅限於可信任的 HTML 內容,如後端產生的按鈕)
1439         * ⚠️ 警告:使用此函式前請確保資料來源安全
1440         * @param {string} data - 資料
1441         * @param {string} type - 類型
1442         * @param {object} row - 整列資料
1443         * @returns {string} - 原始 HTML
1444         * @example columns: [{ data: 'actions', render: UTK.safeRender.trusted }]
1445         */
1446        trusted: function (data, type, row) {
1447            // 直接返回,不做轉義 (僅用於後端產生的可信任 HTML)
1448            return data;
1449        }
1450    };
1451
1452    /**
1453      * 使用DataTable.js套件
1454      * @param {列表本身元素} element
1455      * @param {列表以外需顯示的元素} element2 
1456      * @param {套件的option} options
1457      * @returns
1458      */
1459    UTK.DataTable = function (element, element2, options) {
1460        let opt = UTK.defaultOptions.DataTableOptions(options);
1461
1462        //opt.scrollY = '500px';
1463        opt.ordering = (opt.utkorder) ? true : false;
1464
1465        opt.drawCallback = function (settings) {
1466            options.drawCallback(settings);
1467            $(element).find('td.tooltip').each(function () {
1468                $(this).attr('title', $(this).text()).attr('data-bs-toggle', 'tooltip').attr('data-bs-custom-class', 'tooltip-primary').attr('data-bs-placement', 'top');
1469            });
1470            var tooltipTriggerList = $(element).find('[data-bs-toggle="tooltip"]');
1471            const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
1472
1473            $(element).find('.addcomma').each(function () {
1474                var comma = /\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g;
1475                var num = $(this).text();
1476                num = num.toString().replace(comma, ',');
1477                $(this).text(num);
1478            });
1479        }
1480
1481        if (typeof options.ajax != 'undefined') {
1482            opt.ajax.type = "POST";
1483            opt.serverSide = true;
1484
1485            //判斷後端回應錯誤
1486            opt.ajax.dataSrc = function (json) {
1487                if (json._returnCode) {
1488                    if (json._returnCode != "0000") {
1489                        UTK.Notice(json._returnMessage);
1490                        return false;
1491                    }
1492                }
1493                $(element2).show();
1494                //過濾XSS攻擊
1495                var XSSData = UTK.sanitizeResponse(json.data);
1496                return XSSData;
1497            }
1498        }
1499
1500
1501
1502        $(element).on('preInit.dt', function (e, settings) {
1503            if (!(typeof opt.blockElement == 'undefined' || opt.blockElement == null || opt.blockElement == '')) {
1504                UTK.block(opt.blockElement);
1505            }
1506        })
1507
1508        $(element).on('init.dt', function (e, settings) {
1509        });
1510
1511        $(element).on('draw.dt', function (e, settings) {
1512            if (!(typeof opt.blockElement == 'undefined' || opt.blockElement == null || opt.blockElement == '')) {
1513                UTK.unblock(opt.blockElement);
1514            }
1515        });
1516
1517        $(element).on('error.dt', function (e, settings, techNote, message) {
1518            if (!(typeof opt.blockElement == 'undefined' || opt.blockElement == null || opt.blockElement == '')) {
1519                UTK.unblock(opt.blockElement);
1520            }
1521            try {
1522                let result = JSON.parse(settings.jqXHR.responseText);
1523                if (result._returnCode != undefined) {
1524                    Swal.fire(result._returnMessage, "Code=" + result._returnCode, "error");
1525                }
1526            }
1527            catch {
1528            }
1529        })
1530
1531        var dataTable = $(element).DataTable(opt);
1532        if (opt.ShowScroll) {
1533            $(element2).find('.dataTables_scroll').append('<div class="utktable-scroll-button utktable-left-button"></div>')
1534                .append('<div class="utktable-scroll-button utktable-right-button"></div>')
1535                .append('<div class="utktable-scroll-button utktable-top-button"></div>')
1536                .append('<div class="utktable-scroll-button utktable-bottom-button"></div>');
1537
1538            const scrollContainer = $(element2).find('.dataTables_scrollBody');
1539            const leftButton = $(element2).find('.utktable-left-button');
1540            const rightButton = $(element2).find('.utktable-right-button');
1541            const topButton = $(element2).find('.utktable-top-button');
1542            const bottomButton = $(element2).find('.utktable-bottom-button');
1543
1544            leftButton.on('mouseover', () => UTK.startScrolling($(element2), -1, 'horizontal'));
1545            leftButton.on('mouseout', UTK.stopScrolling);
1546
1547            rightButton.on('mouseover', () => UTK.startScrolling($(element2), 1, 'horizontal'));
1548            rightButton.on('mouseout', UTK.stopScrolling);
1549
1550            topButton.on('mouseover', () => UTK.startScrolling($(element2), -1, 'vertical'));
1551            topButton.on('mouseout', UTK.stopScrolling);
1552
1553            bottomButton.on('mouseover', () => UTK.startScrolling($(element2), 1, 'vertical'));
1554            bottomButton.on('mouseout', UTK.stopScrolling);
1555
1556            // 初始按鈕顯示狀態
1557            if (scrollContainer.scrollLeft() === 0) { leftButton.hide(); }
1558            if (scrollContainer.scrollLeft() + scrollContainer.width() >= scrollContainer[0].scrollWidth) { rightButton.hide(); }
1559            if (scrollContainer.scrollTop() === 0) { topButton.hide(); }
1560            if (scrollContainer.scrollTop() + scrollContainer.height() >= scrollContainer[0].scrollHeight) { bottomButton.hide(); }
1561            scrollContainer.scroll(function () {
1562                if (scrollContainer.scrollLeft() > 0) { leftButton.show(); }
1563                else { leftButton.hide(); }
1564
1565                if (scrollContainer.scrollLeft() < (scrollContainer[0].scrollWidth - scrollContainer.width() - 0.5)) { rightButton.show(); }
1566                else { rightButton.hide(); }
1567
1568                if (scrollContainer.scrollTop() > 0) { topButton.show(); }
1569                else { topButton.hide(); }
1570
1571                if (scrollContainer.scrollTop() < (scrollContainer[0].scrollHeight - scrollC
1571ontainer.height())) { bottomButton.show(); }
1572                else { bottomButton.hide(); }
1573            });
1574        }
1575        return dataTable;
1576    };
1577    /**
1578     * 遞歸地對回應中的每個字串進行轉義處理
1579     * @param {Object|string} response - 來自伺服器的回應數據,可以是字串或物件
1580     * @returns {Object|string} - 已經轉義處理過的安全數據
1581     */
1582    UTK.sanitizeResponse = function (response) {
1583        if (typeof response === 'string') {
1584            // 如果回應是一個字串,進行 HTML 轉義
1585            return UTK.escapeHtml(response);
1586        } else if (typeof response === 'object') {
1587            // 如果回應是一個物件,遞歸地處理每個屬性
1588            for (let key in response) {
1589                if (response.hasOwnProperty(key)) {
1590                    response[key] = UTK.sanitizeResponse(response[key]);
1591                }
1592            }
1593        }
1594        return response;
1595    }
1596    /**
1597     * 將不安全的 HTML 字符轉義成安全的字符
1598     * @param {string} unsafe - 可能包含不安全字符的字串
1599     * @returns {string} - 轉義後的安全字串
1600     */
1601    UTK.escapeHtml = function (unsafe) {
1602        return unsafe
1603            .replace(/&/g, "&amp;")    // 將 & 替換為 &amp;
1604            .replace(/</g, "&lt;")     // 將 < 替換為 &lt;
1605            .replace(/>/g, "&gt;")     // 將 > 替換為 &gt;
1606            .replace(/"/g, "&quot;")   // 將 " 替換為 &quot;
1607            .replace(/'/g, "&#039;");  // 將 ' 替換為 &#039;
1608    }
1609
1610    let scrollInterval;
1611
1612    UTK.startScrolling = function (table, direction, axis) {
1613        scrollInterval = setInterval(() => {
1614            const scrollContainer = table.find('.dataTables_scrollBody');
1615            const leftButton = table.find('.utktable-left-button');
1616            const rightButton = table.find('.utktable-right-button');
1617            const topButton = table.find('.utktable-top-button');
1618            const bottomButton = table.find('.utktable-bottom-button');
1619            if (axis === 'horizontal') {
1620                scrollContainer.scrollLeft(scrollContainer.scrollLeft() + direction * 20); // 調整滾動速度
1621            } else if (axis === 'vertical') {
1622                scrollContainer.scrollTop(scrollContainer.scrollTop() + direction * 20); // 調整滾動速度
1623            }
1624
1625            // 檢查左右按鈕顯示狀態
1626            if (scrollContainer.scrollLeft() === 0) {
1627                leftButton.hide();
1628            } else {
1629                leftButton.show();
1630            }
1631
1632            if (scrollContainer.scrollLeft() + scrollContainer.width() >= scrollContainer[0].scrollWidth) {
1633                rightButton.hide();
1634            } else {
1635                rightButton.show();
1636            }
1637
1638            // 檢查上下按鈕顯示狀態
1639            if (scrollContainer.scrollTop() === 0) {
1640                topButton.hide();
1641            } else {
1642                topButton.show();
1643            }
1644
1645            if (scrollContainer.scrollTop() + scrollContainer.height() >= scrollContainer[0].scrollHeight) {
1646                bottomButton.hide();
1647            } else {
1648                bottomButton.show();
1649            }
1650        }, 50);
1651    }
1652
1653    UTK.stopScrolling = function () {
1654        clearInterval(scrollInterval);
1655    }
1656
1657    /* ================================================
1658    * KeyUp
1659    * ================================================ */
1660    UTK.KeyupEngNum = function (element) {
1661        $(element).keyup(function () {
1662            var x = $(this).val();
1663            x = x.replace(/[\W]/g, '');
1664            $(this).val(x);
1665        })
1666        $(element).on('keyup input', function () {
1667            var x = $(this).val();
1668            x = x.replace(/[\W]/g, '');
1669            $(this).val(x);
1670        })
1671    }
1672    UTK.KeyupNum = function (element) {
1673        $(element).keyup(function () {
1674            var x = $(this).val();
1675            x = x.replace(/[\D]/g, '');
1676            $(this).val(x);
1677        })
1678        $(element).on('keyup input', function () {
1679            var x = $(this).val();
1680            x = x.replace(/[\D]/g, '');
1681            $(this).val(x);
1682        })
1683    }
1684    UTK.ValidateEmail = function (email) {
1685        return email.match(
1686            /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
1687        );
1688    };
1689
1690    /**
1691         * 倒數計時
1692         * @param {any} duration
1693         * @param {any} element
1694         * @param {any} zerofunction
1695         */
1696    UTK.StartCountDown = function (duration, element, zerofunction) {
1697
1698        let secondsRemaining = duration;
1699        let min = 0;
1700        let sec = 0;
1701
1702        let countInterval = setInterval(function () {
1703
1704            min = parseInt(secondsRemaining / 60);
1705            sec = parseInt(secondsRemaining % 60);
1706
1707            element.text(`${paddedFormat(min)}:${paddedFormat(sec)}`);
1708
1709            secondsRemaining = secondsRemaining - 1;
1710            if (secondsRemaining < 0) { zerofunction(); clearInterval(countInterval) };
1711
1712        }, 1000);
1713
1714        return countInterval;
1715    }
1716    var paddedFormat = function (num) {
1717        return num < 10 ? "0" + num : num;
1718    }
1719
1720
1721    UTK.ShowPassword = function () {
1722        $('.ShowPassword').click(function () {
1723            if ($(this).find('i').hasClass('fa-eye')) {
1724                $(this).find('i').remove();
1725                $(this).append('<i class="fa-solid fa-eye-slash"></i>');
1726                $(this).parent().find('input').attr('type', 'text');
1727            }
1728            else {
1729                $(this).find('i').remove();
1730                $(this).append('<i class="fa-solid fa-eye"></i>');
1731                $(this).parent().find('input').attr('type', 'password');
1732            }
1733        });
1734    }
1735
1736    UTK.OpenScanner = function (ModalPanel, ElementId, FuncitonName) {
1737        var data = new Object();
1738        data.ElementId = ElementId;
1739        data.FuncitonName = FuncitonName;
1740
1741        var options = {
1742            data: JSON.stringify(data),
1743            showError: true,
1744            success: function (html) {
1745                $(ModalPanel).html(html);
1746            }
1747        };
1748        UTK.loadHtml("/scanner", options);
1749    }
1750
1751    /**
1752     * JsonParese時遇到特殊字元處理
1753     * @param {any} input
1754     * @returns
1755     */
1756    UTK.CorrectJsonParese = function (input) {
1757        return input.replace(/[\u0000-\u001F\u007F-\u009F]/g, function (char) {
1758            switch (char) {
1759                case '\b': return '\\b';
1760                case '\t': return '\\t';
1761                case '\n': return '\\n';
1762                case '\f': return '\\f';
1763                case '\r': return '\\r';
1764                default: return '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0');
1765            }
1766        });
1767    }
1768    /**
1769     * 複製文字到剪貼簿
1770     */
1771    UTK.CopyText = function (text) {
1772        if (navigator.clipboard && window.isSecureContext) {
1773            // 使用 Clipboard API 複製文字
1774            return navigator.clipboard.writeText(text)
1775                .then(() => {
1776                    console.log("文字已成功複製!");
1777                })
1778                .catch(err => {
1779                    console.error("使用 Clipboard API 複製失敗:", err);
1780                });
1781        } else {
1782            // 傳統 fallback 機制:建立隱藏的 textarea 元素並使用 execCommand
1783            let textArea = document.createElement("textarea");
1784            textArea.value = text;
1785            // 設定 textarea 的樣式,確保元素不會影響頁面佈局
1786            textArea.style.position = "fixed";
1787            textArea.style.top = "0";
1788            textArea.style.left = "0";
1789            textArea.style.width = "1px";
1790            textArea.style.height = "1px";
1791            textArea.style.padding = "0";
1792            textArea.style.border = "none";
1793            textArea.style.outline = "none";
1794            textArea.style.boxShadow = "none";
1795            textArea.style.background = "transparent";
1796            document.body.appendChild(textArea);
1797            textArea.focus();
1798            textArea.select();
1799
1800            try {
1801                const successful = document.execCommand("copy");
1802                if (successful) {
1803                    console.log("使用 execCommand 複製成功!");
1804                } else {
1805                    console.error("execCommand 複製失敗!");
1806                }
1807            } catch (err) {
1808                console.error("execCommand 發生錯誤:", err);
1809            }
1810            document.body.removeChild(textArea);
1811            return Promise.resolve();
1812        }
1813    }
1814
1815    /* ================================================
1816     * return
1817     * ================================================ */
1818
1819    //$.fn.dataTable.ext.errMode = 'none';
1820
1821    return UTK;
1822}))

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.