소스 검색

20220913 後台功能更新

LuluFJ.Ho 2 년 전
부모
커밋
1631f81c69

+ 176
- 0
app/Http/Controllers/Backend/DataManagement/SignupManagementController.php 파일 보기

@@ -0,0 +1,176 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers\Backend\DataManagement;
4
+
5
+use Illuminate\Http\Request;
6
+use Illuminate\Support\Facades\Storage;
7
+use App\Http\Services\CheckParamService;
8
+use App\Http\Controllers\Controller;
9
+use App\Http\Services\Backend\DataManagement\SignupManagementService;
10
+use Log;
11
+use Illuminate\Support\Facades\Session;
12
+use Spatie\SimpleExcel\SimpleExcelWriter;
13
+
14
+class SignupManagementController extends Controller
15
+{
16
+    
17
+    // 相關私有服務層調用器宣告
18
+    private $checkParamSv;
19
+    private $signupManagementSv;
20
+    
21
+    public function __construct()
22
+    {
23
+        // 建構服務層調用器
24
+        $this->checkParamSv=new CheckParamService();
25
+        $this->signupManagementSv = new SignupManagementService();
26
+    }
27
+    public function index()
28
+    {
29
+        // 取得參數
30
+        $param = $_GET;
31
+        // 渲染
32
+        return view('admin.DataManagement.SignupManagement');
33
+
34
+    }
35
+    public function grid()
36
+    {
37
+        // 取得參數
38
+        $param = $_GET;
39
+        // if ($param == null) exit();
40
+        $draw = $param["draw"]; //客戶端傳來的查詢次數,無條件回傳用以核對
41
+        $orderColumn = $param["order"][0]["column"] + 1; //前端從 0 開始送,但 mysql 從 1 開始算
42
+        $orderDir = $param["order"][0]["dir"];
43
+        $start = $param["start"];   // 頁碼
44
+        $length = $param["length"]; // 一頁多大
45
+        $searchValue = $param["search"]["value"];
46
+        //客製化搜尋欄位
47
+        $keyword = $param["columns"][1]["search"]["value"];
48
+
49
+        // 驗證
50
+        if ($keyword != filter_var($keyword, FILTER_SANITIZE_SPECIAL_CHARS)) $keyword = "___CANNOT_FIND_STRING___";
51
+        if (!$this->checkParamSv->LenMToN($keyword, 0, 50)) $keyword = "___CANNOT_FIND_STRING___";
52
+        
53
+        //資料庫
54
+        $recordsTotal = 0;
55
+        if ($keyword) {
56
+            $result=$this->signupManagementSv->getList($recordsTotal, $orderColumn, $orderDir, $start, $length, $searchValue, $this->safeEncrypt(($keyword), 'arm'));
57
+        } else {
58
+            $result=$this->signupManagementSv->getList($recordsTotal, $orderColumn, $orderDir, $start, $length, $searchValue, '');
59
+        }
60
+        
61
+        // 外部短網址系統串接
62
+        $orlCodeIds = array();
63
+
64
+        // 整理返回資料
65
+        $data = array();
66
+        $registeredSession = '';
67
+        $lunchOptions = '';
68
+        for ($i = 0; $i < count($result); $i++) {
69
+            
70
+            $data[] = array(
71
+                //一般資料
72
+                $result[$i]["id"],
73
+                htmlspecialchars($this->safeDecrypt($result[$i]["firstName"], 'arm')),
74
+                htmlspecialchars($this->safeDecrypt($result[$i]["lastName"], 'arm')),
75
+                htmlspecialchars($this->safeDecrypt($result[$i]["companyName"], 'arm')),
76
+                htmlspecialchars($result[$i]["country"]),
77
+                htmlspecialchars($result[$i]["trackNo"]),
78
+                htmlspecialchars($result[$i]["registeredSession"]),
79
+                htmlspecialchars($result[$i]["lunchOptions"]),
80
+                $result[$i]["createDate"],
81
+            );
82
+        }
83
+        $json = array(
84
+            "draw"            => $draw,
85
+            "recordsTotal"    => $recordsTotal,
86
+            "recordsFiltered" => $recordsTotal, //其實還是填入所有筆數,本次筆數可從陣列取得
87
+            "data"            => $data,
88
+        );
89
+        
90
+        // 返回
91
+        return json_decode(json_encode($json), true);
92
+    }
93
+
94
+    public function export(Request $request)
95
+    {
96
+        // 取得參數
97
+        $param = $_POST;
98
+        /*
99
+        if (!$request->keyword) {
100
+            Session::flash('msg', '請填入關鍵字!');
101
+            return redirect()->back();
102
+        }
103
+        */
104
+        if ($request->keyword) {
105
+            $datas = $this->signupManagementSv->getExportList($this->safeEncrypt(($request->keyword), 'arm'));
106
+        } else {
107
+            $datas = $this->signupManagementSv->getExportList('');
108
+        }
109
+        
110
+        $rows = [];
111
+        foreach ($datas as $data) {
112
+            
113
+            if ($data['backupEmail']) {
114
+                $backupEmail = $this->safeDecrypt($data['backupEmail'], 'arm');
115
+            } else {
116
+                $backupEmail = '';
117
+            }
118
+
119
+            $rows[] = [
120
+                '序號' => $data['id'],
121
+                'firstName' => $this->safeDecrypt($data['firstName'], 'arm'),
122
+                'lastName' => $this->safeDecrypt($data['lastName'], 'arm'),
123
+                'companyName' => $this->safeDecrypt($data['companyName'], 'arm'),
124
+                'companyEmail' => $this->safeDecrypt($data['companyEmail'], 'arm'),
125
+                'backupEmail' => $backupEmail,
126
+                'phoneNumber' => $this->safeDecrypt($data['phoneNumber'], 'arm'),
127
+                'country' => $data['country'],
128
+                'trackNo' => $data['trackNo'],
129
+                'registeredSession(TW only)' => $data['registeredSession'],
130
+                'lunchOptions(TW only)' => $data['lunchOptions'],
131
+                'typeOfIndustry' => $data['typeOfIndustry'],
132
+                'typeOfJob' => $data['typeOfJob'],
133
+                'jobTitle' => $data['jobTitle'],
134
+                'trackOfInterest' => $data['trackOfInterest'],
135
+                'areaOfInterest' => $data['areaOfInterest'],
136
+                'howToKnowAboutTheEvent' => $data['howToKnowAboutTheEvent'],
137
+                'consentAcceptEmail' => $data['consentAcceptEmail'],
138
+                'consentPrivacyPolicy' => $data['consentPrivacyPolicy'],
139
+                '報名時間' => $data['createDate'],
140
+            ];
141
+        }
142
+
143
+        SimpleExcelWriter::streamDownload('報名資料'.date('YmdHis').'.xlsx')
144
+            ->addRows($rows)
145
+            ->toBrowser();
146
+    }
147
+
148
+    /**
149
+     * 參數加解密模組: 加密部分,建議使用環境變數中的 secret key 作加解密種子
150
+     */
151
+    public function safeEncrypt(string $message, string $skey): string
152
+    {
153
+        
154
+        $strArr = str_split(base64_encode($message));
155
+        $strCount = count($strArr);
156
+        foreach (str_split($skey) as $key => $value)
157
+        $key < $strCount && $strArr[$key].=$value;
158
+        return str_replace(array('=', ' ', '/'), array('O0O0O', 'o000o', 'oo00o'), join('', $strArr));
159
+    }
160
+    
161
+    /**
162
+     * 參數加解密模組: 解密部分,建議使用環境變數中的 secret key 作加解密種子
163
+     */
164
+    public function safeDecrypt(string $encrypted, string $skey): string
165
+    {
166
+        $strArr = str_split(str_replace(array('O0O0O', 'o000o', 'oo00o'), array('=', ' ', '/'), $encrypted), 2);
167
+        $strCount = count($strArr);
168
+
169
+        foreach (str_split($skey) as $key => $value) {
170
+            $key <= $strCount && isset($strArr[$key]) && $strArr[$key][1] === $value && $strArr[$key] = $strArr[$key][0];
171
+        }
172
+            
173
+        return base64_decode(join('', $strArr));
174
+    }
175
+    
176
+}

+ 239
- 0
app/Http/Controllers/Backend/DataManagement/TrackManagementController.php 파일 보기

@@ -0,0 +1,239 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers\Backend\DataManagement;
4
+
5
+use Illuminate\Http\Request;
6
+use Illuminate\Support\Facades\Storage;
7
+use App\Http\Services\CheckParamService;
8
+use App\Http\Controllers\Controller;
9
+use App\Http\Services\Backend\DataManagement\TrackManagementService;
10
+use Log;
11
+
12
+class TrackManagementController extends Controller
13
+{
14
+    
15
+    // 相關私有服務層調用器宣告
16
+    private $checkParamSv;
17
+    private $trackManagementSv;
18
+    
19
+    public function __construct()
20
+    {
21
+        // 建構服務層調用器
22
+        $this->checkParamSv=new CheckParamService();
23
+        $this->trackManagementSv = new TrackManagementService();
24
+    }
25
+    public function index()
26
+    {
27
+        // 渲染
28
+        return view('admin.DataManagement.TrackManagement');
29
+
30
+    }
31
+    public function grid()
32
+    {
33
+        // 取得參數
34
+        $param = $_GET;
35
+        if ($param == null) exit();
36
+        $draw = $param["draw"]; //客戶端傳來的查詢次數,無條件回傳用以核對
37
+        $orderColumn = $param["order"][0]["column"] + 1; //前端從 0 開始送,但 mysql 從 1 開始算
38
+        $orderDir = $param["order"][0]["dir"];
39
+        $start = $param["start"];   // 頁碼
40
+        $length = $param["length"]; // 一頁多大
41
+        $searchValue = $param["search"]["value"];
42
+        //客製化搜尋欄位
43
+        $keyword = $param["columns"][1]["search"]["value"] ?? null ;
44
+
45
+        // 驗證
46
+        if ($keyword != filter_var($keyword, FILTER_SANITIZE_SPECIAL_CHARS)) $keyword = "___CANNOT_FIND_STRING___";
47
+        if (!$this->checkParamSv->LenMToN($keyword, 0, 50)) $keyword = "___CANNOT_FIND_STRING___";
48
+        
49
+        //資料庫
50
+        $recordsTotal = 0;
51
+
52
+        $result=$this->trackManagementSv->getList($recordsTotal, $orderColumn, $orderDir, $start, $length, $searchValue, $keyword);
53
+
54
+        // 整理返回資料
55
+        $data = array();
56
+        $registeredSession = '';
57
+        $lunchOptions = '';
58
+        for ($i = 0; $i < count($result); $i++) {
59
+            
60
+            $result[$i]["ctrEdit"]=' 
61
+                <a class="btn _btn-warning btn-sm _fz-12" href="track/edit/'.$result[$i]["id"].'"><span class="_glyphicon _glyphicon-list"></span> 編輯 </a>
62
+                <a class="btn btn-danger btn-sm _fz-12" onclick="return delWarning();" href="track/delete/'.$result[$i]["id"].'"><span class="_glyphicon _glyphicon-remove"></span> 刪除 </a>';
63
+
64
+            $data[] = array(
65
+                //一般資料
66
+                $result[$i]["id"],
67
+                htmlspecialchars($result[$i]["trackNo"]),
68
+                htmlspecialchars($result[$i]["trackTitle"]),
69
+                htmlspecialchars($result[$i]["trackLimit"]),
70
+                $result[$i]["createDate"],
71
+                $result[$i]["ctrEdit"],
72
+            );
73
+        }
74
+        $json = array(
75
+            "draw"            => $draw,
76
+            "recordsTotal"    => $recordsTotal,
77
+            "recordsFiltered" => $recordsTotal, //其實還是填入所有筆數,本次筆數可從陣列取得
78
+            "data"            => $data,
79
+        );
80
+        
81
+        // 返回
82
+        return json_decode(json_encode($json), true);
83
+    }
84
+
85
+    /**
86
+     * 新增訊息頁面
87
+     *   本端點非 API,直接到 view
88
+     * @return array:          返回渲染頁
89
+     */
90
+    public function create()
91
+    {
92
+        // 取得參數與驗證
93
+        // 服務層取得資料(以及實現各種業務上的邏輯)
94
+        
95
+        $tagUnassign = $this->tagManagementSv->getTagByReferral("", false);
96
+        //有一個referral 貼多個標的情況
97
+        $tagUnassign_uniques = array();
98
+        foreach ($tagUnassign as $c) {
99
+            $tagUnassign_uniques[$c["id"].'-'.$c["tagName"]] = $c; // Get unique country by code.
100
+        }
101
+        
102
+        $tagAssigned = $this->tagManagementSv->getTagByReferral("", true);
103
+        //有一個referral 貼多個標的情況
104
+        $tagAssigned_uniques = array();
105
+        foreach ($tagAssigned as $c) {
106
+            $tagAssigned_uniques[$c["id"].'-'.$c["tagName"]] = $c; // Get unique country by code.
107
+        }
108
+        $referralList = $this->referralSv->getReferral();
109
+        $referral=array();
110
+        foreach($referralList as $data)
111
+        {
112
+            array_push($referral,$data["referral"]);
113
+        }
114
+        // 渲染
115
+        return view('admin.DataManagement.ReferralManagementEdit', [
116
+            'operdata'        => "",
117
+            'richmenuId'         => 0,
118
+            'richmenus'          => $this->richmenuManagementSv->getRichmenuList(),
119
+            'tagUnassign'        => $tagUnassign_uniques, 
120
+            'tagAssigned'        => $tagAssigned_uniques,
121
+            'existReferral'        => $referral,
122
+        ]);
123
+    }
124
+
125
+    /**
126
+     * 編輯訊息頁面
127
+     *   本端點非 API,依照變量取得資料後,渲染到 view
128
+     *
129
+     * @param integer $id :    訊息的序號
130
+     *
131
+     * @return array:          返回渲染頁
132
+     */
133
+    public function edit($id)
134
+    {
135
+        // 取得參數與驗證
136
+        // 服務層取得資料(以及實現各種業務上的邏輯)
137
+        $res = $this->trackManagementSv->getTrackDataById($id);
138
+
139
+        // 渲染
140
+        return view('admin.DataManagement.TrackManagementEdit', [
141
+            'operdata'      => $res,
142
+            'id'            => $id,
143
+        ]);
144
+    }
145
+    
146
+    /**
147
+     * 新增或編輯標籤的端點
148
+     *   本端點非 API,依照參數新增或修改資料後,跳轉到既有的 view
149
+     *
150
+     * @param Request $request :  整包參數
151
+     *
152
+     * @return array:             返回跳轉渲染頁的資訊
153
+     */
154
+    public function store(Request $request)
155
+    {
156
+        // 取得參數(由於是內部系統因此不做後端驗證)
157
+        \Log::info($request->all());
158
+        // return;
159
+        $mode = $request->operateMode;
160
+        $trackNo = $request->trackNo;
161
+        $trackTitle = $request->trackTitle;
162
+        $trackLimit = $request->trackLimit;
163
+        // $id = ($request->mode == 'insert') ? '' : $request->id;
164
+        // $name = $request->name;
165
+        // $referral = $request->referral;
166
+        // $orlCode = $request->orlCode;
167
+        // $richmenuId = $request->richmenuId;
168
+        // $hidTag = $request->hidTag;
169
+        // $folderS3 = 'urlQRCode'; //要儲存到S3上的folder名稱
170
+        /**
171
+         * picsee短網址
172
+         */
173
+        // 服務層設置(以及實現各種業務上的邏輯)
174
+        if ($mode='insert') {
175
+            // 新增模式
176
+            // 直接創建一組短網址
177
+            //$orlCode = $this->insertOrl($name, env('LINE_LIFF_URL').'/'.env('LIFF_PAGE_LOGIN').'?referral='.$referral.'&liffId='.env('LIFF_PAGE_LOGIN'), $name);
178
+            // $orlCode = $this->insertOrl($name, env('APP_URL').'/web/referrallogin/'.$referral, $name);
179
+            /**
180
+             * 改掉以往存到ORL的方式,改以透過Picsee來產生短網址
181
+             * 增加儲存欄位
182
+             * @picseeCode 短網址存取碼
183
+             * @picseeUrl 短網址
184
+             * @qRCode_pic
185
+             */
186
+            //新的新增至PICSEE
187
+            // $picseeData = $this->insertPicsee(env("APP_FRONTEND_URL").'/web/referrallogin/'.$referral);
188
+
189
+            // $picseeUrl = str_replace(env("PICSEE_SERCH_DOMAIN"),env("PICSEE_REPLACE_DOMAIN"),$picseeData->picseeUrl);
190
+            // $explodeGetCode = explode("/",$picseeUrl);
191
+            // $picseeCode = end($explodeGetCode);
192
+            // $qRCode = $this->makeQRCode($picseeCode,$picseeUrl,$folderS3);
193
+            // $id = $this->trackManagementSv->insertReferral(
194
+            //     $name,
195
+            //     $referral,
196
+            //     $richmenuId,
197
+            //     $picseeCode,
198
+            //     $picseeUrl,
199
+            //     $qRCode,
200
+            //     $orlCode
201
+            // );
202
+        } elseif ($mode=='edit') {
203
+//             $referralData = $this->referralSv->getReferralById($id);
204
+//             // 編輯模式
205
+//             if ($orlCode != 0) {
206
+//                 $this->updateOrl($orlCode, $name, $name);
207
+//             } else {
208
+//                 // 針對導入短網址服務之後,但又已經存在的紀錄,開放只要編輯過就可以取得短網址
209
+//                 //$orlCode = $this->insertOrl($name, env('LINE_LIFF_URL').'/'.env('LIFF_PAGE_LOGIN').'?referral='.$referral.'&liffId='.env('LIFF_PAGE_LOGIN'), $name);
210
+// //                $orlCode = $this->insertOrl($name, env('APP_URL').'/web/referrallogin/'.$referral, $name);
211
+//                 //此段取得短網址的功能調整指向picsee流程
212
+//                 if($referralData["picseeCode"] == ""){
213
+//                     $picseeData = $this->insertPicsee(env("APP_URL").'/web/referrallogin/'.$referral);
214
+//                     $picseeUrl = str_replace(env("PICSEE_SERCH_DOMAIN"),env("PICSEE_REPLACE_DOMAIN"),$picseeData->picseeUrl);
215
+//                     $explodeGetCode = explode("/",$picseeUrl);
216
+//                     $picseeCode = end($explodeGetCode);
217
+//                     $qRCode = $this->makeQRCode($picseeCode,$picseeUrl,$folderS3);
218
+//                     $this->referralSv->modifyReferralPicsee(
219
+//                         $id,
220
+//                         $picseeCode,
221
+//                         $picseeUrl,
222
+//                         $qRCode
223
+//                     );
224
+//                 }
225
+//             }
226
+            $this->trackManagementSv->store(
227
+                $id,
228
+                $trackNo,
229
+                $trackTitle,
230
+                $trackLimit,
231
+            );
232
+        }
233
+        // $this->referralTagManagementSv->modifyreferralTagByReferralAndTagIds($id, $hidTag);
234
+    
235
+        // 跳轉
236
+        return redirect('/backend/dataManagement/track');
237
+    }
238
+    
239
+}

+ 87
- 0
app/Http/Services/Backend/DataManagement/SignupManagementService.php 파일 보기

@@ -0,0 +1,87 @@
1
+<?php
2
+
3
+namespace App\Http\Services\Backend\DataManagement;
4
+
5
+use App\Http\Services\ConstDef\GeneralConst;
6
+use App\Models\SignupData;
7
+use DB;
8
+use Log;
9
+use PhpOffice\PhpSpreadsheet\IOFactory;
10
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
11
+
12
+class SignupManagementService
13
+{
14
+    
15
+    // 相關私有 model 調用器宣告
16
+    private $signupDb;
17
+    public function __construct()
18
+    {
19
+        // 建構 model 調用器
20
+        $this->signupDb=new SignupData();
21
+    }
22
+    public function getList(&$cnt = 0, $orderColumn, $orderDir, $start, $length, $searchValue, $keyword)
23
+    {
24
+        // 調用資料庫(或者其他業務邏輯)
25
+        $res = $this->signupDb
26
+            ->select('*');
27
+            
28
+        if ($keyword != ""){
29
+
30
+            $res->where('firstName', $keyword)
31
+                ->orWhere('lastName', $keyword)
32
+                ->orWhere('companyName', $keyword)
33
+                ->orWhere('companyEmail', $keyword)
34
+                ->orWhere('backupEmail', $keyword)
35
+                ->orWhere('phoneNumber', $keyword)
36
+                ->orWhere('country', $keyword)
37
+                ->orWhere('trackNo', $keyword)
38
+                ->orWhere('typeOfIndustry', $keyword)
39
+                ->orWhere('typeOfJob', $keyword)
40
+                ->orWhere('jobTitle', $keyword);
41
+        }
42
+            
43
+        // 取總筆數
44
+        $cnt = $res->count();
45
+        // 排序
46
+        $res = $res
47
+            ->orderByRaw((int)$orderColumn . ' ' . $orderDir);
48
+        // 分頁
49
+        $res = $res
50
+            ->skip($start)->take($length);
51
+        // 實際取資料
52
+        $result = $res
53
+            ->get()
54
+            ->toArray();
55
+        
56
+        // 整理返回值並返回
57
+        return $result;
58
+    }
59
+
60
+    public function getExportList($keyword)
61
+    {
62
+        $res = $this->signupDb
63
+            ->select('*');
64
+            
65
+        if ($keyword != ""){
66
+
67
+            $res->where('firstName', $keyword)
68
+                ->orWhere('lastName', $keyword)
69
+                ->orWhere('companyName', $keyword)
70
+                ->orWhere('companyEmail', $keyword)
71
+                ->orWhere('backupEmail', $keyword)
72
+                ->orWhere('phoneNumber', $keyword)
73
+                ->orWhere('country', $keyword)
74
+                ->orWhere('trackNo', $keyword)
75
+                ->orWhere('typeOfIndustry', $keyword)
76
+                ->orWhere('typeOfJob', $keyword)
77
+                ->orWhere('jobTitle', $keyword);
78
+        }
79
+        $res = $this->signupDb
80
+            ->get()
81
+            ->toArray();
82
+        
83
+        // 整理返回值並返回
84
+        return $res;
85
+    }
86
+
87
+}

+ 120
- 0
app/Http/Services/Backend/DataManagement/TrackManagementService.php 파일 보기

@@ -0,0 +1,120 @@
1
+<?php
2
+
3
+namespace App\Http\Services\Backend\DataManagement;
4
+
5
+use App\Http\Services\ConstDef\GeneralConst;
6
+use App\Models\TrackData;
7
+use DB;
8
+use Log;
9
+use PhpOffice\PhpSpreadsheet\IOFactory;
10
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
11
+
12
+class TrackManagementService
13
+{
14
+    
15
+    // 相關私有 model 調用器宣告
16
+    private $signupDb;
17
+    public function __construct()
18
+    {
19
+        // 建構 model 調用器
20
+        $this->trackDataDb = new TrackData();
21
+
22
+    }
23
+    public function getList(&$cnt = 0, $orderColumn, $orderDir, $start, $length, $searchValue, $keyword)
24
+    {
25
+        // 調用資料庫(或者其他業務邏輯)
26
+        $res = $this->trackDataDb
27
+            ->select('*')
28
+            ->where('trackNo', 'like', '%'.$keyword.'%')
29
+            ->orWhere('trackTitle', 'like', '%'.$keyword.'%');
30
+            
31
+        // 取總筆數
32
+        $cnt = $res->count();
33
+        // 排序
34
+        $res = $res
35
+            ->orderByRaw((int)$orderColumn . ' ' . $orderDir);
36
+        // 分頁
37
+        $res = $res
38
+            ->skip($start)->take($length);
39
+        // 實際取資料
40
+        $result = $res
41
+            ->get()
42
+            ->toArray();
43
+        
44
+        // 整理返回值並返回
45
+        return $result;
46
+    }
47
+
48
+    public function getReferral()
49
+    {
50
+        $res=$this->referralDB->select(["id","name","referral"])->get();
51
+        if(!empty($res))
52
+        {
53
+            $res=$res->toArray();
54
+        }
55
+        return $res;
56
+    }
57
+    public function getTrackDataById($id)
58
+    {
59
+        $res=$this->trackDataDb
60
+            ->select('*')
61
+            ->where("id",$id)
62
+            ->first();
63
+
64
+        if(!empty($res))
65
+        {
66
+            $res=$res->toArray();
67
+        }
68
+        return $res;
69
+    }
70
+    public function getReferralByReferral($referral)
71
+    {
72
+        return $this->referralDB->where('referral', $referral)->first();
73
+    }
74
+    public function modifyReferral($id,$name,$referral,$richmenuId,$orlCode=0)
75
+    {
76
+        $res=$this->referralDB->where("id",$id)->update(["name"=>$name,"richmenuId"=>$richmenuId,"orlCode"=>$orlCode]);
77
+        return $res;
78
+    }
79
+    public function modifyReferralPicsee($id,$picseeCode,$picseeUrl,$qRCode)
80
+    {
81
+        $res=$this->referralDB->where("id",$id)->update(["picseeCode"=>$picseeCode,"shortUrl"=>$picseeUrl,"qRCode"=>$qRCode]);
82
+        return $res;
83
+    }
84
+    public function modifyPicseeCntAndTime($id,$picseeCnt,$last_call)
85
+    {
86
+        $res=$this->referralDB->where("id",$id)->update(["picseeCnt"=>$picseeCnt,"last_call"=>$last_call]);
87
+        return $res;
88
+    }
89
+    public function insertReferral($name,$referral,$richmenuId,$picseeCode,$picseeUrl,$qRCode,$orlCode=0)
90
+    {
91
+        $res=$this->referralDB->insert(["name"=>$name,"referral"=>$referral,"richmenuId"=>$richmenuId,"picseeCode"=>$picseeCode,"shortUrl"=>$picseeUrl,"qRCode"=>$qRCode,"orlCode"=>$orlCode]);
92
+        $id = \DB::getPdo()->lastInsertId();
93
+        return $id;
94
+    }
95
+    public function getExportList($keyword)
96
+    {
97
+        $res = $this->signupDb
98
+            ->select('*')
99
+            // 過濾搜尋條件
100
+            ->where('firstName', $keyword)
101
+            ->orWhere('lastName', $keyword)
102
+            ->orWhere('companyName', $keyword)
103
+            ->orWhere('companyEmail', $keyword)
104
+            ->orWhere('backupEmail', $keyword)
105
+            ->orWhere('phoneNumber', $keyword)
106
+            ->orWhere('country', $keyword)
107
+            ->orWhere('trackNo', $keyword)
108
+            // ->orWhere('registeredSession', $keyword)
109
+            // ->orWhere('lunchOptions', $keyword)
110
+            ->orWhere('typeOfIndustry', $keyword)
111
+            ->orWhere('typeOfJob', $keyword)
112
+            ->orWhere('jobTitle', $keyword)
113
+            ->get()
114
+            ->toArray();
115
+        
116
+        // 整理返回值並返回
117
+        return $res;
118
+    }
119
+
120
+}

+ 49
- 0
app/Http/Services/CheckParamService.php 파일 보기

@@ -0,0 +1,49 @@
1
+<?php
2
+
3
+namespace App\Http\Services;
4
+
5
+use DateTime;
6
+
7
+class CheckParamService
8
+{
9
+    
10
+    public function __construct()
11
+    {
12
+    }
13
+    
14
+    public function LenMToN($str, $m, $n) {
15
+        if (mb_strlen($str) >= $m && mb_strlen($str) <= $n) {
16
+            return true;
17
+        } else {
18
+            return false;
19
+        }
20
+    }
21
+    
22
+    public function isascii($str) {
23
+        if (mb_detect_encoding($str) == "ASCII") {
24
+            return true;
25
+        } else {
26
+            return false;
27
+        }
28
+    }
29
+    
30
+    public function isengnum($str) {
31
+        return (preg_match("/^[a-zA-Z0-9]+$/", $str) == 1);
32
+    }
33
+    
34
+    public function isnum($str) {
35
+        return (preg_match("/^[0-9]+$/", $str) == 1);
36
+    }
37
+    
38
+    public function validateDate($date, $format = 'Y-m-d H:i:s') {
39
+        $d = DateTime::createFromFormat($format, $date);
40
+        return $d && $d->format($format) == $date;
41
+    }
42
+    
43
+    public function RemoveChars($str, $chars) {
44
+        $chars = str_split($chars, 1);
45
+        for ($i = 0; $i < count($chars); $i++) $str = str_replace($chars[$i], "", $str);
46
+        return $str;
47
+    }
48
+    
49
+}

+ 2
- 1
composer.json 파일 보기

@@ -11,7 +11,8 @@
11 11
         "laravel/framework": "^8.75",
12 12
         "laravel/sanctum": "^2.11",
13 13
         "laravel/tinker": "^2.5",
14
-        "league/flysystem-aws-s3-v3": "^1.0"
14
+        "league/flysystem-aws-s3-v3": "^1.0",
15
+        "spatie/simple-excel": "^1.13"
15 16
     },
16 17
     "require-dev": {
17 18
         "facade/ignition": "^2.5",

+ 137
- 0
resources/views/admin/DataManagement/SignupManagement.blade.php 파일 보기

@@ -0,0 +1,137 @@
1
+@extends('admin.master')
2
+
3
+@section('content')
4
+    <div class="row">
5
+        <div class="col-lg-12">
6
+            <div class="contentpanel">
7
+                <!--<form id="listForm" method="post">-->
8
+                <form id='searchForm' action="{{ route('signup.export') }}" method="POST">
9
+                    @csrf
10
+                    <!-- 功能按鈕(新增/批量處理等等) -->
11
+                    <div class="row" style="margin-bottom: 5px;">
12
+                        <div class="col-lg-12">
13
+                            <ol class="headermenu">
14
+                                
15
+                                <li>
16
+                                    <button class="btn btn-darkblue btn-xs" id="export"><strong>匯出</strong></button>
17
+                                </li>
18
+                            </ol>
19
+                        </div>
20
+                    </div>
21
+                    <!-- 搜尋段 -->
22
+                    <div class="panel panel-default">
23
+                        <div class="panel-heading _panel-heading" data-bs-toggle="collapse" data-bs-target="#search_content">
24
+                            <h3 class="panel-title m-0">報名資料</h3>
25
+                        </div>
26
+                        <div id="search_content" class="collapse show">
27
+                            <div class="panel-body row">
28
+                                <!-- 關鍵字 -->
29
+                                <div class="form-group _form-group col-sm-12 col-md-6 col-xl-3">
30
+                                    <label for="keyword">關鍵字(可搜尋欄位:first name, last name, company name, company email, backup email, phone number)</label>
31
+                                    <input type="text" class="form-control input-sm" id="keyword" name="keyword" maxlength="50">
32
+                                </div>
33
+                                <!-- Search -->
34
+                                <div class="form-group _form-group col-sm-12 col-md-12 col-xl-12">
35
+                                    <a class="btn btn-success btn-sm _fz-12" onclick="javascript: custom_search();">
36
+                                        <div class="_glyphicon _glyphicon-search"></div>
37
+                                    </a>
38
+                                </div>
39
+                            </div>
40
+                        </div>
41
+                    </div>
42
+                    <hr class="search-hr"/>
43
+                    <!-- 列表段 -->
44
+                    <div class="row">
45
+                        <div class="col-lg-12">
46
+                            <table id="GridView1" class="table table-striped table-bordered" cellspacing="0"
47
+                                   width="100%">
48
+                                <thead>
49
+                                <tr>
50
+                                    <th>序號</th>
51
+                                    <th>first name</th>
52
+                                    <th>last name</th>
53
+                                    <th>company name</th>
54
+                                    <th>country</th>
55
+                                    <th>track no</th>
56
+                                    <th>session(TW only)</th>
57
+                                    <th>lunch(TW only)</th>
58
+                                    <th>報名時間</th>
59
+                                    
60
+                                </tr>
61
+                                </thead>
62
+                            </table>
63
+                        </div>
64
+                    </div>
65
+                </form>
66
+                <!-- row -->
67
+            </div>
68
+        </div>
69
+    </div>
70
+@endsection
71
+
72
+@section('extjs')
73
+    <script>
74
+        $(document).ready(function () {
75
+            var table = $('#GridView1').dataTable({
76
+                "scrollX": true,
77
+                "processing": true,
78
+                "serverSide": true,
79
+                "ajax": "signup/grid",
80
+                "paging": true,
81
+                "ordering": true,
82
+                "info": true,
83
+                "order": [[0, "desc"]],
84
+                "stateSave": true,
85
+                "pagingType": "full",
86
+                "bFilter": true,
87
+                "aoColumnDefs": [{
88
+                    'bSortable': false,
89
+                    // 'aTargets': [0] //不想參加排序的欄位,可指定多個,逗號分隔
90
+                }],
91
+                "fnDrawCallback":function( oSettings ) {
92
+                    table.find('.downloadBtn').on('click',function(){
93
+                        let urlToDownload = $(this).data('for-download');
94
+                        let downloadFilename = urlToDownload.split('/');
95
+                        forceDownload($(this).data('for-download'),downloadFilename[downloadFilename.length-1]);
96
+                    });
97
+                }
98
+            }); 
99
+            // 從網址參觸發搜尋
100
+            custom_search()
101
+            $('#GridView1_filter').hide();
102
+        });
103
+
104
+        //客製化搜尋欄位
105
+        function custom_search() {
106
+            $('#GridView1').DataTable()
107
+                .column(1).search($('#keyword').val())
108
+            ;
109
+            $('#GridView1').dataTable().fnDraw(true);
110
+        }
111
+        
112
+        function forceDownload(url, fileName){
113
+            var xhr = new XMLHttpRequest();
114
+            xhr.withCredentials = true;
115
+            xhr.open("GET", url, true);
116
+            xhr.responseType = "blob";
117
+            xhr.onload = function(){
118
+                var urlCreator = window.URL || window.webkitURL;
119
+                var imageUrl = urlCreator.createObjectURL(this.response);
120
+                var tag = document.createElement('a');
121
+                tag.href = imageUrl;
122
+                tag.download = fileName;
123
+                document.body.appendChild(tag);
124
+                tag.click();
125
+                document.body.removeChild(tag);
126
+            }
127
+            xhr.send();
128
+        }
129
+
130
+        // 匯出報告
131
+        $("#export").click(function () {
132
+
133
+            $('#searchForm').submit();
134
+        });
135
+        
136
+    </script>
137
+@endsection

+ 105
- 0
resources/views/admin/DataManagement/TrackManagement.blade.php 파일 보기

@@ -0,0 +1,105 @@
1
+@extends('admin.master')
2
+
3
+@section('content')
4
+    <div class="row">
5
+        <div class="col-lg-12">
6
+            <div class="contentpanel">
7
+                <!--<form id="listForm" method="post">-->
8
+                <form id='searchForm' action="{{ route('signup.export') }}" method="POST">
9
+                    @csrf
10
+                    <!-- 功能按鈕(新增/批量處理等等) -->
11
+                    <div class="row" style="margin-bottom: 5px;">
12
+                        <div class="col-lg-12">
13
+                            <ol class="headermenu">
14
+                                <li>
15
+                                    <button class="btn btn-darkblue btn-xs" id="create"><strong>新增</strong></button>
16
+                                </li>
17
+                                <!--<li>
18
+                                    <button class="btn btn-darkblue btn-xs" id="export"><strong>匯出</strong></button>
19
+                                </li>-->
20
+                            </ol>
21
+                        </div>
22
+                    </div>
23
+                    <!-- 搜尋段 -->
24
+                    <div class="panel panel-default">
25
+                        <div class="panel-heading _panel-heading" data-bs-toggle="collapse" data-bs-target="#search_content">
26
+                            <h3 class="panel-title m-0">場次查詢</h3>
27
+                        </div>
28
+                        <div id="search_content" class="collapse show">
29
+                            <div class="panel-body row">
30
+                                <!-- 關鍵字 -->
31
+                                <div class="form-group _form-group col-sm-12 col-md-6 col-xl-3">
32
+                                    <label for="keyword">關鍵字(可搜尋欄位:track no, track title)</label>
33
+                                    <input type="text" class="form-control input-sm" id="keyword" name="keyword" maxlength="50">
34
+                                </div>
35
+                                <!-- Search -->
36
+                                <div class="form-group _form-group col-sm-12 col-md-12 col-xl-12">
37
+                                    <a class="btn btn-success btn-sm _fz-12" onclick="javascript: custom_search();">
38
+                                        <div class="_glyphicon _glyphicon-search"></div>
39
+                                    </a>
40
+                                </div>
41
+                            </div>
42
+                        </div>
43
+                    </div>
44
+                    <hr class="search-hr"/>
45
+                    <!-- 列表段 -->
46
+                    <div class="row">
47
+                        <div class="col-lg-12">
48
+                            <table id="GridView1" class="table table-striped table-bordered" cellspacing="0"
49
+                                   width="100%">
50
+                                <thead>
51
+                                <tr>
52
+                                    <th>序號</th>
53
+                                    <th>場次 no</th>
54
+                                    <th>場次備註(track title)</th>
55
+                                    <th>場次人數上限(track limit)</th>
56
+                                    <th>建立時間</th>
57
+                                    <th>操作</th>
58
+                                </tr>
59
+                                </thead>
60
+                            </table>
61
+                        </div>
62
+                    </div>
63
+                </form>
64
+                <!-- row -->
65
+            </div>
66
+        </div>
67
+    </div>
68
+@endsection
69
+
70
+@section('extjs')
71
+    <script>
72
+        $(document).ready(function () {
73
+            var table = $('#GridView1').dataTable({
74
+                "scrollX": true,
75
+                "processing": true,
76
+                "serverSide": true,
77
+                "ajax": "track/grid",
78
+                "paging": true,
79
+                "ordering": true,
80
+                "info": true,
81
+                "order": [[0, "desc"]],
82
+                "stateSave": true,
83
+                "pagingType": "full",
84
+                "bFilter": true,
85
+                "aoColumnDefs": [{
86
+                    'bSortable': false,
87
+                    // 'aTargets': [0] //不想參加排序的欄位,可指定多個,逗號分隔
88
+                }]
89
+            }); 
90
+            // 從網址參觸發搜尋
91
+            // custom_search()
92
+            $('#GridView1_filter').hide();
93
+        });
94
+
95
+        //客製化搜尋欄位
96
+        function custom_search() {
97
+            $('#GridView1').DataTable()
98
+                .column(1).search($('#keyword').val())
99
+            ;
100
+            $('#GridView1').dataTable().fnDraw(true);
101
+        }
102
+        
103
+
104
+    </script>
105
+@endsection

+ 973
- 0
resources/views/admin/DataManagement/TrackManagementEdit.blade.php 파일 보기

@@ -0,0 +1,973 @@
1
+@extends('admin.master')
2
+
3
+@section('content')
4
+    <div class="row">
5
+        <div class="col-xl-12">
6
+            <form id="EditForm" class="form-horizontal" method="post"
7
+                  action="{{ url('/backend/dataManagement/track/store') }}" enctype="multipart/form-data">
8
+                {{ csrf_field() }}
9
+                <div class="panel _panel-primary">
10
+                    <div class="panel-heading _panel-heading">
11
+                        <h4 class="panel-title m-0">{{ ($operdata == "") ? "新增 " : "修改 " }}議程資料</h4>
12
+                    </div>
13
+                    <div class="panel-body row">
14
+                        <div>
15
+                            <table class="table" style="border-collapse:collapse;">
16
+                                <tbody class="msgTbody">
17
+                                <input type="hidden" id="operateMode" value="edit"/>
18
+                                <input type="hidden" id="id" value="{{$id}}"/>
19
+                                
20
+                                <!-- 欄位:圖文頭貼設定 -->
21
+                                <tr class="msgTemplate">
22
+                                    <td class="header-require col-xl-2">議程資料設定</td>
23
+                                    <td>
24
+                                        <div id="imgmapcontent-tab1">
25
+                                            <input type="hidden" id="tab1MenuFlag"/>
26
+                                            <input type="hidden" id="tab1ImagemapModelType" value="m1">
27
+                                            <input type="hidden" id="tab1ImagemapImgSize" value='{"width":800,"height":800}'>
28
+                                            
29
+                                            <!-- 表格本體 -->
30
+                                            <table class="table" id="DetailsView1" style="border-collapse:collapse;">
31
+                                                <tbody>
32
+                                                <!-- 序號 -->
33
+                                                @if ($operdata == "")
34
+                                                    <!-- Insert Mode -->
35
+                                                    <input type="hidden" name="mode" value="insert"/>
36
+                                                @else
37
+                                                    <!-- Edit Mode -->
38
+                                                    <input type="hidden" name="mode" value="edit"/>
39
+                                                    <tr>
40
+                                                        <td class="col-xl-2">序號</td>
41
+                                                        <td>
42
+                                                            <input name="id" type="hidden" value="{{ $operdata['id'] }}" id="id"/>
43
+                                                            {{ $operdata['id'] }}
44
+                                                        </td>
45
+                                                    </tr>
46
+                                                @endif                                
47
+                                                    <!-- 欄位:標題 -->
48
+                                                    <!-- ALL Mode -->
49
+                                                    <tr>
50
+                                                        <td class="header-require col-xl-2">trackNo</td>
51
+                                                        <td>
52
+                                                            <div class="col-xl-3 nopadding">
53
+                                                                @if ($operdata == "")
54
+                                                                    <input name="trackNo" type="text" value="" maxlength="100"
55
+                                                                        id="trackNo" class="form-control">
56
+                                                                @else
57
+                                                                    <input name="trackNo" type="text"
58
+                                                                        value="{{ $operdata['trackNo'] }}" maxlength="100"
59
+                                                                        id="trackNo" class="form-control">
60
+                                                                @endif
61
+                                                                <label class="error" for="trackNo"></label>
62
+                                                            </div>
63
+                                                        </td>
64
+                                                    </tr>
65
+                                                    <!-- 欄位:說明 -->
66
+                                                    <!-- ALL Mode -->
67
+                                                    <tr>
68
+                                                        <td class="header-require col-xl-2">trackTitle</td>
69
+                                                        <td>
70
+                                                            <div class="col-xl-3 nopadding">
71
+                                                                @if ($operdata == "")
72
+                                                                    <input name="trackTitle" type="text" value="" maxlength="500"
73
+                                                                        id="trackTitle"
74
+                                                                        class="form-control">
75
+                                                                @else
76
+                                                                    <input name="trackTitle" type="text"
77
+                                                                        value="{{ $operdata['trackTitle'] }}"
78
+                                                                        maxlength="500" id="trackTitle" class="form-control">
79
+                                                                @endif
80
+                                                                <label class="error" for="trackTitle"></label>
81
+                                                            </div>
82
+                                                        </td>
83
+                                                    </tr>
84
+                                                    <!-- 欄位:說明 -->
85
+                                                    <!-- ALL Mode -->
86
+                                                    <tr>
87
+                                                        <td class="header-require col-xl-2">trackLimit</td>
88
+                                                        <td>
89
+                                                            <div class="col-xl-3 nopadding">
90
+                                                                @if ($operdata == "")
91
+                                                                    <input name="trackLimit" type="text" value="" maxlength="500"
92
+                                                                        id="trackLimit"
93
+                                                                        class="form-control">
94
+                                                                @else
95
+                                                                    <input name="trackLimit" type="text"
96
+                                                                        value="{{ $operdata['trackLimit'] }}"
97
+                                                                        maxlength="500" id="trackLimit" class="form-control">
98
+                                                                @endif
99
+                                                                <label class="error" for="trackLimit"></label>
100
+                                                            </div>
101
+                                                        </td>
102
+                                                    </tr>
103
+                                                </tbody>
104
+                                            </table>
105
+                                        
106
+                                            <hr>
107
+
108
+                                        </div>
109
+                                    </td>
110
+                                </tr>
111
+
112
+                                <!-- 下控制按鈕 -->
113
+                                <tr>
114
+                                    <td>&nbsp;</td>
115
+                                    <td>
116
+                                        <div style="text-align: right">
117
+                                            <input type="button" id="btnSave" value="儲存" class="btn btn-primary" style="margin-right: 5px;">
118
+                                            <input type="button" id="btnBackToList" value="返回" class="btn btn-secondary">
119
+                                        </div>
120
+                                    </td>
121
+                                </tr>
122
+                                </tbody>
123
+                            </table>
124
+                        </div>
125
+                    </div>
126
+                </div>
127
+            </form>
128
+        </div>
129
+    </div>
130
+
131
+    <div id="ajax-spinner" style="display:none;">
132
+        <img src="{{ asset('assets/images/backend_spinner.svg') }}" />
133
+    </div>
134
+
135
+    <link rel="stylesheet" href="{{url('assets/resize-controller/project/resizeControl.css')}}">
136
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
137
+    <style>
138
+        /* --------------- 全域 --------------- */
139
+
140
+        .msgTemplate div.form-group _form-group {
141
+            margin: 0;
142
+        }
143
+
144
+        .tab-content {
145
+            -webkit-box-shadow: none;
146
+            -moz-box-shadow: none;
147
+            box-shadow: none;
148
+        }
149
+
150
+        /* --------------- Imagemap --------------- */
151
+
152
+        .imagemap-model-small-container {
153
+            display: flex;
154
+            flex-wrap: wrap;
155
+        }
156
+
157
+        .imagemap-model-small-container .imagemap-model-small {
158
+            width: 80px;
159
+            height: 55px;
160
+            margin: 10px;
161
+        }
162
+
163
+        .imagemap-model-small-container .table {
164
+            width: 100%;
165
+            height: 100%;
166
+            cursor: pointer;
167
+        }
168
+
169
+        .imagemap-model-small-container .table td {
170
+            background-color: #cecece;
171
+            color: #ffffff;
172
+            border: 1px solid #858484;
173
+        }
174
+
175
+        .imagemap-model-small-container > .imagemap-model-small.active td {
176
+            border-color: #6c62f3;
177
+            border-width: 2px;
178
+        }
179
+
180
+        /* --------------- imagemap-model --------------- */
181
+
182
+        .imagemap-model {
183
+            width: 240px;
184
+            height: 166px;
185
+        }
186
+
187
+        .imagemap-model > table {
188
+            width: 100%;
189
+            height: 100%;
190
+            text-align: center;
191
+        }
192
+
193
+        .imagemap-model > table > tbody td {
194
+            background-color: #bfbfbf;
195
+            color: #ffffff;
196
+            vertical-align: middle;
197
+        }
198
+
199
+        .imagemap-model > table > tbody td.active {
200
+            background-color: #58ae58;
201
+        }
202
+
203
+        .imagemap-model > table > tbody td:not(.active):hover {
204
+            background-color: #898989c9;
205
+            cursor: default;
206
+        }
207
+
208
+        /* --------------- uri 綁定標籤 ----------------- */
209
+
210
+        .select2-container--default .select2-selection--multiple .select2-selection__choice {
211
+            background-color: #5897fb;
212
+            border-radius: 10px;
213
+            color: white;
214
+            padding-top: 5px;
215
+            padding-bottom: 5px;
216
+        }
217
+
218
+        .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
219
+            border-right: unset;
220
+            color: #fff;
221
+            font-size: 1.2em;
222
+            left: 1px;
223
+            top: 3px;
224
+        }
225
+
226
+        .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover, .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus {
227
+            background-color: #5897fb;
228
+            color: #fff;
229
+        }
230
+
231
+        .select2-selection.select2-selection--multiple {
232
+            min-height: 24px;
233
+        }
234
+
235
+        .select2-container--default .select2-search--inline .select2-search__field {
236
+            min-height: 24px;
237
+            font-size: 14px;
238
+        }
239
+
240
+        /* --------------- AJAX upload --------------- */
241
+
242
+        .image-preview-wrapper{
243
+            position: relative;
244
+            cursor: pointer;
245
+        }
246
+        .spinner-wrapper {
247
+            position: absolute;
248
+            opacity: 0;
249
+            margin: 0;
250
+            padding: 0;
251
+            /*left: 50%;*/
252
+            /*top: 50%;*/
253
+            transform: translate(-50%, -50%);
254
+
255
+            width: 100px;
256
+            height: 100px;
257
+            background-image: url(/assets/images/backend_spinner.svg);
258
+            background-size: 50px;
259
+            background-repeat: no-repeat;
260
+            background-position: center;
261
+        }
262
+        .opacity-1 {
263
+            opacity: 1;
264
+        }
265
+    </style>
266
+@endsection
267
+
268
+@section('extjs')
269
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
270
+    <script src="{{url('assets/js/jquery.mousewheel-3.0.6.min.js')}}"></script>
271
+    <script src="{{url('assets/js/jquery.mCustomScrollbar.min.js')}}"></script>
272
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.1/babel.min.js"></script>
273
+    <script src="{{url('assets/resize-controller/project/index.js')}}"></script>
274
+    <script>
275
+        var imagemapBoxes = {};
276
+
277
+        $(document).ready(function () {
278
+            // 下控制按鈕 儲存
279
+            $("#btnSave").click(function () {
280
+                
281
+                finishEdit();
282
+            });
283
+
284
+            // 下控制按鈕 返回
285
+            $("#btnBackToList").click(function () {
286
+                location.href = '/backend/dataManagement/track';
287
+            });
288
+
289
+            // 註冊事件
290
+            eventImagemap();
291
+            eventUriAction();
292
+
293
+        });
294
+
295
+        function restoreMsgInfo(msgInfo) {
296
+            // 初始化
297
+            var tabId = 'tab1';
298
+            var menuInfoObj = JSON.parse(msgInfo).menuInfo;
299
+            // console.log(menuInfoObj);
300
+
301
+            // var menuFlag = menuInfoObj.menuFlag;
302
+            // var modelType = menuInfoObj.modelType;
303
+            // var chatBarText = menuInfoObj.chatBarText;
304
+            // var displayByDefault = menuInfoObj.displayByDefault;
305
+            var imgUrl = menuInfoObj.imgUrl;
306
+            // var imgSize = menuInfoObj.imgSize;
307
+            // var imagemapActions = menuInfoObj.actions;
308
+
309
+            // 還原內容
310
+            $('#'+tabId+'MenuFlag').val(menuFlag);
311
+            $("#"+tabId+"ImagemapModelType").val(modelType);
312
+            $("#chatBarText").val(chatBarText);
313
+            if (!displayByDefault) {
314
+                $("input[name='displayByDefault']").filter('[value="0"]').prop('checked', true);
315
+            }
316
+            $("img[data-target='"+tabId+"-image-preview-imagemapImg']").attr("src", imgUrl);
317
+
318
+            // 點擊 選擇的版型
319
+            // trigger: $('.imagemap-model-small').click()
320
+            var $chosen_model = $("#imgmapcontent-"+tabId).find('.imagemap-model-small').filter("[data-model_type='"+modelType+"']");
321
+            if (modelType === 'mcustom') {
322
+                $chosen_model.trigger('click', [menuInfoObj]);
323
+            } else {
324
+                $chosen_model.click();
325
+            }
326
+
327
+            if (modelType == 'mjson') {
328
+                // todo 重新組合 ImagemapJson
329
+                var imagemapJson = '';
330
+                $("#"+tabId+"ImagemapJson").val(imagemapJson);
331
+            } else {
332
+                // 還原 每個按鈕的值
333
+                $.each(imagemapActions, function(i, actionObj) {
334
+                    var btnNum = i + 1;
335
+                    $("#"+tabId+"Imagemapbtn"+btnNum+"Act").val(actionObj.type);
336
+                    $("#"+tabId+"Imagemapbtn"+btnNum+"Act").trigger('change'); // 觸發事件切換按鈕N動作
337
+                    $("#"+tabId+"Imagemapbtn"+btnNum+"Content").val(actionObj.content);
338
+
339
+                    // 前往選單
340
+                    if (actionObj.type == 'richmenu') {
341
+                        $("#"+tabId+"Imagemapbtn"+btnNum+"Submenu").val(actionObj.content);
342
+                        $("#"+tabId+"Imagemapbtn"+btnNum+"Submenu").trigger('change');
343
+
344
+                        if ('keywordValue' in actionObj) {
345
+                            $("#"+tabId+"Imagemapbtn"+btnNum+"Content").attr('data-keyword_value', actionObj.keywordValue);
346
+                        }
347
+                    }
348
+
349
+                    // 貼標 (開啟連結)
350
+                    if ('tags' in actionObj) {
351
+                        $("#"+tabId+"Imagemapbtn"+btnNum+"Tags").val(actionObj.tags);
352
+                        $("#"+tabId+"Imagemapbtn"+btnNum+"Tags").trigger('change');
353
+                    }
354
+                });
355
+            }
356
+        }
357
+        
358
+        // 完成編輯
359
+        function finishEdit() {
360
+            var msgInfoObj = msgInfo();
361
+            // console.log(msgInfoObj);
362
+            // return;
363
+
364
+            if ('err' in msgInfoObj) {
365
+                alert("有錯誤:\n" + msgInfoObj.err.join("\n"));
366
+                return;
367
+            }
368
+
369
+            $.blockUI({
370
+                message: $('#ajax-spinner'),
371
+                css: {
372
+                    backgroundColor: 'transparent',
373
+                    border: '0'
374
+                },
375
+            });
376
+
377
+            $.ajax({
378
+                type: "POST",
379
+                url: '/backend/dataManagement/track/store',
380
+                data: {
381
+                    "operateMode": $("#operateMode").val(),
382
+                    "trackNo": $("#trackNo").val(),
383
+                    "trackTitle": $("#trackTitle").val(),
384
+                    "trackLimit": $("#trackLimit").val(),
385
+                    "_token":"{{ csrf_token() }}",
386
+
387
+                    "id": $("#id").val(),
388
+                },
389
+                success: function(result) {
390
+                    
391
+                    if (result.code === 0) {
392
+                        alert('儲存成功');
393
+                        location.href = '/backend/dataManagement/track';
394
+                    } else {
395
+                        alert(result.message);
396
+                        alert('123');
397
+                    }
398
+                },
399
+                fail: function(jqXHR, textStatus) {
400
+                    console.log(jqXHR);
401
+                    console.log(textStatus);
402
+                },
403
+                complete: function() {
404
+                    $.unblockUI();
405
+                }
406
+            });
407
+        }
408
+
409
+        function eventImagemap() {
410
+            // 選擇版型
411
+            $('.imagemap-model-small').unbind("click");
412
+            $('.imagemap-model-small').click(function (event, imagemapContent) {
413
+                var $model_small = $(this);
414
+                var $model_small_container = $model_small.parent('.imagemap-model-small-container');
415
+                var $imagemap_content = $model_small.closest('div[id^="imgmapcontent-tab"]');
416
+                var $imagemap_btns = $imagemap_content.children('.imagemap-btn'); // 此頁籤下所有按鈕區域
417
+
418
+                var tabNum = 1;
419
+                var tabId = 'tab'+tabNum;
420
+                var modelType = $model_small.attr('data-model_type');
421
+                var imgSizeObj = JSON.parse($('#'+tabId+'ImagemapImgSize').val());
422
+
423
+                // 三種版型類型的容器
424
+                var $imagemap_model = $imagemap_content.find('.imagemap-model');
425
+                var $custom_container = $('#'+tabId+'ImagemapBoxContainer');
426
+                var $json_container = $('#'+tabId+'ImagemapJson').closest('div.row');
427
+
428
+                if ($model_small.hasClass('active')) {
429
+                    return;
430
+                }
431
+
432
+                // 設定選擇的 active
433
+                $model_small_container.find('.imagemap-model-small').removeClass('active');
434
+                $model_small.addClass('active');
435
+
436
+                // 設定 model type
437
+                $('#'+tabId+'ImagemapModelType').val(modelType);
438
+
439
+                imagemapClearBtns($imagemap_btns, tabId);
440
+
441
+                if (modelType == 'mjson') {
442
+                    $imagemap_model.hide();
443
+                    $custom_container.hide();
444
+                    $json_container.show();
445
+                    $imagemap_btns.hide();
446
+                }
447
+                else if (modelType == 'mcustom') {
448
+                    $imagemap_model.hide();
449
+                    $custom_container.show();
450
+                    $json_container.hide();
451
+
452
+                    var boxNum = 1; // ImagemapBox1
453
+                    // 要等 ImagemapBox HTML 完全顯示才能繪製,所以等 300ms 才初始化
454
+                    setTimeout(function() {
455
+                        imagemapInitBox(tabNum, boxNum, imgSizeObj, imagemapContent);
456
+
457
+                        // 設定背景圖案 (initImagemapBox 後才能設定)
458
+                        var imagemapUrl = $('img[data-target="'+tabId+'-image-preview-imagemapImg"]').attr('src');
459
+                        $('#ImagemapBox'+boxNum).css('background-image', 'url(' + imagemapUrl + ')');
460
+                    }, 300);
461
+                }
462
+                else {
463
+                    $imagemap_model.show();
464
+                    $custom_container.hide();
465
+                    $json_container.hide();
466
+
467
+                    // 建立對應的 imagemap-model
468
+                    $imagemap_model.children('.table').remove();
469
+                    $model_small.children('.table').clone(false).appendTo($imagemap_model);
470
+                    $imagemap_model.find('td').each(function (index) {
471
+                        var btnNum = index + 1;
472
+                        if (btnNum === 1) $(this).addClass('active');
473
+                        $(this).html('按鈕'+btnNum);
474
+                        $(this).attr('data-btn', btnNum);
475
+                    });
476
+                }
477
+            });
478
+
479
+            // 編輯按鈕區域
480
+            $('.imagemap-model').unbind("click");
481
+            $('.imagemap-model').on('click', 'td', function () {
482
+                var $clicked_td = $(this);
483
+                var $table = $clicked_td.closest('.table');
484
+                var $imagemap_content = $clicked_td.closest('div[id^="imgmapcontent-tab"]');
485
+                var tabNum = '1';
486
+                var btnNum = $clicked_td.attr('data-btn');
487
+
488
+                // 設定選擇的 active
489
+                $table.find('td').removeClass('active');
490
+                $clicked_td.addClass('active');
491
+
492
+                // 顯示對應按鈕
493
+                $imagemap_content.find('.imagemap-btn').hide();
494
+                $('#tab'+'1'+'Imagemapbtn'+btnNum).show();
495
+            });
496
+
497
+            // 切換 按鈕N動作
498
+            $('.imagemap-btn').find('select.imagemapBtnAct').unbind("change");
499
+            $('.imagemap-btn').find('select.imagemapBtnAct').change(function () {
500
+                var tabNum = 1;
501
+                var btnNum = $(this).attr('id').split('Imagemapbtn')[1].replace(/\D/g, ''); // tab1Imagemapbtn1Act
502
+                var type = $(this).val();
503
+
504
+                var $btnContent = $('#tab1Imagemapbtn'+btnNum+'Content');
505
+                var $btnSubmenu = $('#tab1Imagemapbtn'+btnNum+'Submenu');
506
+                var $btnTags = $('#tab1Imagemapbtn'+btnNum+'Tags');
507
+
508
+                // 清空
509
+                $btnContent.val('');
510
+                $btnSubmenu.val(0);
511
+                $btnTags.val(null).trigger('change');
512
+
513
+                // 顯示按鈕對應的元件
514
+                imagemapShowBtnInputsByAction(type, $btnContent, $btnTags, $btnSubmenu);
515
+            });
516
+        }
517
+
518
+        function eventUriAction() {
519
+            $('.js-example-basic-multiple').select2({
520
+                placeholder: '請選擇或輸入以搜尋標籤'
521
+            });
522
+        }
523
+
524
+        function msgInfo() {
525
+            var result = {
526
+                menuInfo: {},
527
+                err: []
528
+            };
529
+
530
+            var msgObj = {};
531
+            var tabNum = '1';
532
+            var tabId = 'tab' + tabNum;
533
+
534
+            // 取資料
535
+            var menuFlag = $('#'+tabId+'MenuFlag').val();
536
+            var modelType = $("#"+tabId+"ImagemapModelType").val();
537
+            var imgUrl = $("img[data-target$='"+tabId+"-image-preview-imagemapImg']").attr("src");
538
+            var json = $("#"+tabId+"ImagemapJson").val();
539
+            var menuTitle = $("#menuTitle").val();
540
+            var chatBarText = $("#chatBarText").val();
541
+            var displayByDefault = $("input[name='displayByDefault']:checked").val();
542
+
543
+            // 檢查
544
+            if (menuTitle === '') result.err.push('標題內容為空');
545
+            if (imgUrl === '/assets/images/placeholder_upload_380_260.png') result.err.push('圖片沒上傳');
546
+            if (modelType == 'mjson' && json === '') result.err.push('JSON內容為空');
547
+            if (chatBarText === '') result.err.push('選單顯示名稱為空');
548
+
549
+            var imagemapActions = '';
550
+            if (modelType == 'mjson') {
551
+                // todo msgInfoImagemapActionsFromJson(json)
552
+                imagemapActions = '';
553
+            } else if (modelType == 'mcustom') {
554
+                imagemapActions = msgInfoImagemapActions(tabNum, modelType, result);
555
+            } else { // 預設版型
556
+                imagemapActions = msgInfoImagemapActions(tabNum, modelType, result);
557
+            }
558
+
559
+            msgObj = {
560
+                // menuFlag: menuFlag,
561
+                // modelType: modelType,
562
+                // name: menuTitle,
563
+                // chatBarText: chatBarText,
564
+                // displayByDefault: !!parseInt(displayByDefault),
565
+                imgUrl: imgUrl,
566
+                // imgSize: {
567
+                //     "width": 800,
568
+                //     "height": 800
569
+                // },
570
+                // actions: imagemapActions,
571
+            };
572
+
573
+            result.menuInfo = msgObj;
574
+
575
+            if (result.err.length === 0) {
576
+                delete result.err;
577
+            }
578
+
579
+            return result;
580
+        }
581
+
582
+        function msgInfoImagemapActions(tabNum, modelType, result) {
583
+            var tabId = 'tab'+tabNum;
584
+
585
+            var areas = [];
586
+            if (modelType == 'mcustom') {
587
+                areas = imagemapBoxes[tabNum].getResultPos();
588
+            } else if (modelType != 'mjson') {
589
+                // 樣板按鈕區域定義 (依照頁面顯示的按鈕順序)
590
+                var modelAreaMap = {
591
+                    "m1": [
592
+                        {"x": 0, "y": 0, "width": 833, "height": 843},
593
+                        {"x": 833, "y": 0, "width": 833, "height": 843},
594
+                        {"x": 1666, "y": 0, "width": 834, "height": 843},
595
+                        {"x": 0, "y": 843, "width": 833, "height": 843},
596
+                        {"x": 833, "y": 843, "width": 833, "height": 843},
597
+                        {"x": 1666, "y": 843, "width": 834, "height": 843},
598
+                    ],
599
+                    "m2": [
600
+                        {"x": 0, "y": 0, "width": 1250, "height": 843},
601
+                        {"x": 1250, "y": 0, "width": 1250, "height": 843},
602
+                        {"x": 0, "y": 843, "width": 1250, "height": 843},
603
+                        {"x": 1250, "y": 843, "width": 1250, "height": 843},
604
+                    ],
605
+                    "m3": [
606
+                        {"x": 0, "y": 0, "width": 2500, "height": 843},
607
+                        {"x": 0, "y": 843, "width": 833, "height": 843},
608
+                        {"x": 833, "y": 843, "width": 833, "height": 843},
609
+                        {"x": 1666, "y": 843, "width": 834, "height": 843},
610
+                    ],
611
+                    "m4": [
612
+                        {"x": 0, "y": 0, "width": 1250, "height": 1686},
613
+                        {"x": 1250, "y": 0, "width": 1250, "height": 843},
614
+                        {"x": 1250, "y": 843, "width": 1250, "height": 843},
615
+                    ],
616
+                    "m5": [
617
+                        {"x": 0, "y": 0, "width": 2500, "height": 843},
618
+                        {"x": 0, "y": 843, "width": 2500, "height": 843},
619
+                    ],
620
+                    "m6": [
621
+                        {"x": 0, "y": 0, "width": 1250, "height": 1686},
622
+                        {"x": 1250, "y": 0, "width": 1250, "height": 1686},
623
+                    ],
624
+                    "m7": [
625
+                        {"x": 0, "y": 0, "width": 2500, "height": 1686},
626
+                    ]
627
+                };
628
+                areas = modelAreaMap[modelType];
629
+            }
630
+
631
+            var actions = [];
632
+            for (var btnNum = 1; btnNum <= areas.length; btnNum++) {
633
+                var btnId = 'btn'+btnNum;
634
+                var type = $("#"+tabId+"Imagemap"+btnId+"Act").val();
635
+                var content = $("#"+tabId+"Imagemap"+btnId+"Content").val();
636
+                var submenuId = $("#"+tabId+"Imagemap"+btnId+"Submenu").val();
637
+                var tagsData = $("#"+tabId+"Imagemap"+btnId+"Tags").select2('data');
638
+                var area = areas[btnNum-1];
639
+
640
+                if (type != 'richmenu' && content === '') result.err.push('按鈕'+btnNum+'的內容為空');
641
+                if (type == 'uri'
642
+                    && !content.startsWith('https://')
643
+                    && !content.startsWith('tel:')
644
+                    && !content.startsWith('line://')
645
+                ) {
646
+                    result.err.push('按鈕'+btnNum+'的連結網址須為 https:// 或 tel:// 或 line://');
647
+                }
648
+
649
+                var action = {
650
+                    "type": type,
651
+                    "content": content,
652
+                    "area": {
653
+                        "x": area.x,
654
+                        "y": area.y,
655
+                        "width": area.width,
656
+                        "height": area.height
657
+                    }
658
+                };
659
+
660
+                if (type == 'uri' && tagsData.length !== 0) { // uri 貼標
661
+                    var tags = [];
662
+                    $.each(tagsData, function(i, item) {
663
+                        tags.push(item.id);
664
+                    });
665
+                    action.tags = tags;
666
+                } else if (type == 'richmenu') { // 前往子選單
667
+                    action.content = submenuId;
668
+
669
+                    var keywordValue = $("#"+tabId+"Imagemapbtn"+btnNum+"Content").attr('data-keyword_value');
670
+                    if (typeof keywordValue === 'undefined') {
671
+                        var menuFlag = $('#tab1MenuFlag').val();
672
+                        keywordValue = 'richmenu-' + menuFlag.replace(/menu-/g, '') + '-' + Date.now();
673
+                    }
674
+                    action.keywordValue = keywordValue;
675
+                }
676
+
677
+                actions.push(action);
678
+            }
679
+            // console.log(actions);
680
+
681
+            return actions
682
+        }
683
+
684
+        function generateFlag(length) {
685
+            var result = '';
686
+            var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
687
+            var charactersLength = characters.length;
688
+            for ( var i = 0; i < length; i++ ) {
689
+                result += characters.charAt(Math.floor(Math.random() * charactersLength));
690
+            }
691
+            return result;
692
+        }
693
+
694
+        function imagemapInitBox(tabNum, boxNum, imgSizeObj, imagemapContent) {
695
+            let imagemapBox;
696
+            var mapData = null;
697
+
698
+            // console.log(imagemapContent);
699
+            if (typeof imagemapContent !== 'undefined') {
700
+                mapData = imagemapContent;
701
+            }
702
+
703
+            imagemapBox = new resizer({
704
+                container: '#ImagemapBox'+boxNum,
705
+                wrap: imgSizeObj,
706
+                wrapperWidth: 380,
707
+                itemDefaultSize: 380,
708
+                add: '#AddImagemapBox'+boxNum,
709
+                delete: {
710
+                    selector: '#DeleteImagemapBox'+boxNum,
711
+                    getDelete: function(res) {
712
+                        // console.log(res);
713
+                        var btnNum = res.index + 1;
714
+                        var tabId = 'tab'+tabNum;
715
+
716
+                        // 重設按鈕的值 (從刪除的下一個的開始往前遞補)
717
+                        for (btnNum; btnNum < 12; btnNum++) {
718
+                            var nextNum = btnNum + 1;
719
+
720
+                            var $currentAct = $('#'+tabId+'Imagemapbtn'+btnNum+'Act');
721
+                            var $currentContent = $('#'+tabId+'Imagemapbtn'+btnNum+'Content');
722
+                            var $currentSubmenu = $('#'+tabId+'Imagemapbtn'+btnNum+'Submenu');
723
+                            var $currentTags = $('#'+tabId+'Imagemapbtn'+btnNum+'Tags');
724
+
725
+                            var $nextAct = $('#'+tabId+'Imagemapbtn'+nextNum+'Act');
726
+                            var $nextContent = $('#'+tabId+'Imagemapbtn'+nextNum+'Content');
727
+                            var $nextSubmenu = $('#'+tabId+'Imagemapbtn'+nextNum+'Submenu');
728
+                            var $nextTags = $('#'+tabId+'Imagemapbtn'+nextNum+'Tags');
729
+
730
+                            // 內容
731
+                            $currentAct.val($nextAct.val());
732
+                            $currentContent.val($nextContent.val());
733
+                            $currentSubmenu.val($nextSubmenu.val());
734
+                            // 標籤
735
+                            var nextTagsData = $nextTags.select2('data');
736
+                            if (nextTagsData.length !== 0) {
737
+                                var nextTags = [];
738
+                                $.each(nextTagsData, function(i, item) {
739
+                                    nextTags.push(item.id);
740
+                                });
741
+                                $currentTags.val(nextTags);
742
+                                $currentTags.trigger('change');
743
+                            }
744
+
745
+                            // 顯示按鈕對應的元件
746
+                            imagemapShowBtnInputsByAction($currentAct.val(), $currentContent, $currentTags, $currentSubmenu);
747
+                        }
748
+
749
+                        // 清除最後一個按鈕
750
+                        $('#'+tabId+'Imagemapbtn12Act').val('postback'); // richmenu 預設
751
+                        $('#'+tabId+'Imagemapbtn12Content').val('');
752
+                        $('#'+tabId+'Imagemapbtn12Submenu').val(0);
753
+                        $('#'+tabId+'Imagemapbtn12Tags').val(null).trigger('change');
754
+
755
+                        // 顯示最後一個按鈕對應的元件
756
+                        imagemapShowBtnInputsByAction('postback', $('#'+tabId+'Imagemapbtn12Content'), $('#'+tabId+'Imagemapbtn12Tags'), $('#'+tabId+'Imagemapbtn12Submenu'));
757
+                    }
758
+                },
759
+                get: '#GetImagemapBox'+boxNum,
760
+                map: mapData,
761
+                getActive: function(res) {
762
+                    // console.log(res);
763
+                    var $imagemapContent = $('#imgmapcontent-tab'+tabNum);
764
+                    var btnNum = res.index + 1;
765
+
766
+                    // 顯示對應按鈕
767
+                    $imagemapContent.find('.imagemap-btn').hide();
768
+                    $('#tab'+tabNum+'Imagemapbtn'+btnNum).show();
769
+                }
770
+            });
771
+
772
+            imagemapBoxes[tabNum] = imagemapBox;
773
+        }
774
+
775
+        function imagemapShowBtnInputsByAction(action, $btnContent, $btnTags, $btnSubmenu) {
776
+            if (action == 'uri') {
777
+                $btnContent.closest('div[class="row"]').show();
778
+                $btnTags.closest('div[class="row"]').show();
779
+                $btnSubmenu.closest('div[class="row"]').hide();
780
+            } else if (action == 'richmenu') {
781
+                $btnContent.closest('div[class="row"]').hide();
782
+                $btnTags.closest('div[class="row"]').hide();
783
+                $btnSubmenu.closest('div[class="row"]').show();
784
+            } else {
785
+                $btnContent.closest('div[class="row"]').show();
786
+                $btnTags.closest('div[class="row"]').hide();
787
+                $btnSubmenu.closest('div[class="row"]').hide();
788
+            }
789
+        }
790
+
791
+        function imagemapClearBtns($imagemap_btns, tabId) {
792
+            var $btnContents = $imagemap_btns.find('.imagemapBtnContent');
793
+            var $btnActs = $imagemap_btns.find('.imagemapBtnAct');
794
+            var $btnSubmenus = $imagemap_btns.find('select.imagemapBtnSubmenu');
795
+            var $btnTags = $imagemap_btns.find('.imagemapBtnTags');
796
+
797
+            // 清除按鈕區域內容
798
+            $btnContents.val('');
799
+            $btnActs.val('postback'); // richmenu 預設
800
+            $btnSubmenus.val(0)
801
+            $btnTags.val(null).trigger('change');
802
+
803
+            // 顯示按鈕對應的元件 (這邊是一次設定所有按鈕下所有元件)
804
+            imagemapShowBtnInputsByAction('postback', $btnContents, $btnTags, $btnSubmenus);
805
+
806
+            // 顯示按鈕1
807
+            $imagemap_btns.hide();
808
+            $('#'+tabId+'Imagemapbtn1').show();
809
+        }
810
+
811
+        // AJAX 上傳圖片/影片
812
+        // http://arsee88.blogspot.com/2020_09_20_archive.html
813
+        var imagePreview = [];
814
+        var spinner = [];
815
+        var fileUploader = [];
816
+        // 註冊上傳圖片的功能(可以重複註冊)
817
+        var imagemapImgId = 'imagemapImg' + '1';
818
+        imagePreview[imagemapImgId] = document.querySelector('[data-target="tab'+'1'+'-image-preview-imagemapImg"]');
819
+        spinner[imagemapImgId] = document.querySelector('[data-target="tab'+'1'+'-spinner-imagemapImg"]');
820
+        fileUploader[imagemapImgId] = document.querySelector('[data-target="tab'+'1'+'-file-uploader-imagemapImg"]');
821
+        fileUploader[imagemapImgId].addEventListener("change", handleFileUpload);
822
+        // File input 上傳檔案時綁定的處理函式
823
+        async function handleFileUpload(e) {
824
+            e.preventDefault();
825
+
826
+            var $file_uploader = $(this);
827
+            var splitItems = $file_uploader.attr('id').split('-');
828
+            // tab1-file-uploader-imagemapImg
829
+            var msgType = splitItems[splitItems.length-1]; // lastItem
830
+            console.log(msgType);
831
+
832
+            var msgNum = '1';
833
+            var itemId = msgType + msgNum;
834
+            console.log(itemId);
835
+
836
+            try {
837
+                const file = e.target.files[0];
838
+                if (!file) {
839
+                    return;
840
+                }
841
+
842
+                setUploading(true, itemId);
843
+
844
+                // 檢查
845
+                const beforeUploadCheck = await checkBeforeUpload(file, msgType, msgNum);
846
+                if (!beforeUploadCheck.isValid) throw beforeUploadCheck.errorMessages;
847
+
848
+                // 上傳
849
+                const arrayBuffer = await getArrayBuffer(file);
850
+                var response = await uploadFileAJAX(arrayBuffer);
851
+                alert("上傳成功");
852
+                setUploading(false, itemId);
853
+                showPreviewImage(response.url, itemId);
854
+
855
+                // 更新 ImagemapBox
856
+                var $imgPreview = $(imagePreview[itemId]);
857
+                if ($imgPreview.attr('data-target').indexOf('imagemap') !== -1) {
858
+                    $imgPreview.closest('#imgmapcontent-tab1').find('.imagemapBox').css('background-image', 'url(' + response.url + ')');
859
+                }
860
+            } catch (error) {
861
+                setUploading(false, itemId);
862
+                alert(error);
863
+                console.log("上傳失敗: ", error);
864
+            } finally {
865
+                e.target.value = '';
866
+            }
867
+        }
868
+        // If you prefer Base64 image, use "FileReader.readAsDataURL"
869
+        function showPreviewImage(url, itemId) {
870
+            imagePreview[itemId].src = url;
871
+        }
872
+        // Change file object into ArrayBuffer
873
+        function getArrayBuffer(fileObj) {
874
+            return new Promise((resolve, reject) => {
875
+                const reader = new FileReader();
876
+                // Get ArrayBuffer when FileReader on load
877
+                reader.addEventListener("load", () => {
878
+                    resolve(reader.result);
879
+                });
880
+
881
+                // Get Error when FileReader on error
882
+                reader.addEventListener("error", () => {
883
+                    reject("error occurred in getArrayBuffer");
884
+                });
885
+
886
+                // read the blob object as ArrayBuffer
887
+                // if you nedd Base64, use reader.readAsDataURL
888
+                reader.readAsArrayBuffer(fileObj);
889
+            });
890
+        }
891
+        // Use "new Uint8Array()"" to change ArrayBuffer into TypedArray (TypedArray is not a truly Array)
892
+        // Use "Array.from()" to change it into Array
893
+        function uploadFileAJAX(arrayBuffer) {
894
+            return fetch("{{ env('APP_URL') }}/backend/dataManagement/eLecturerTeachersList/saveImg", {
895
+                headers: {
896
+                    version: 1,
897
+                    "content-type": "application/json"
898
+                },
899
+                method: "POST",
900
+                body: JSON.stringify({
901
+                    image: Array.from(new Uint8Array(arrayBuffer))
902
+                })
903
+            })
904
+                .then(res => {
905
+                    if (!res.ok) {
906
+                        throw res.statusText;
907
+                    }
908
+                    return res.json();
909
+                })
910
+                .then(data => data)
911
+                .catch(err => console.log("err", err));
912
+        }
913
+        function checkBeforeUpload(fileObject, msgType, msgNum) {
914
+            return new Promise(resolve => {
915
+                let errorMessages = [];
916
+
917
+                // isValidFileType
918
+                const validFileTypes = ["image/jpeg", "image/png" ];
919
+                const isValidFileType = validFileTypes.includes(fileObject.type);
920
+                if (!isValidFileType) {
921
+                    errorMessages.push("只支援 JPG / PNG 格式圖片");
922
+                    resolve({
923
+                        isValid: isValidFileType,
924
+                        errorMessages: errorMessages.join("\n")
925
+                    });
926
+                    return;
927
+                }
928
+
929
+                // isValidFileSize
930
+                const isValidFileSize = (fileObject.size / 1024 / 1024) < 1; // 1 MB
931
+                if (!isValidFileSize) {
932
+                    errorMessages.push("圖片最大 1MB");
933
+                }
934
+                
935
+                // isValidDimension
936
+                var isValidDimension = true;
937
+                const dim = Promise.resolve(getImageSize(fileObject));
938
+                dim.then(function(result) {
939
+                    switch (msgType)
940
+                    {
941
+                        case "imagemapImg":
942
+                            if (result.width != 800 || result.height != 800) {
943
+                                errorMessages.push("圖片尺寸必須為 800x800");
944
+                                isValidDimension = false;
945
+                            }
946
+                            break;
947
+                    }
948
+
949
+                    resolve({
950
+                        isValid: isValidFileType && isValidFileSize && isValidDimension,
951
+                        errorMessages: errorMessages.join("\n")
952
+                    });
953
+                });
954
+            });
955
+        }
956
+        function setUploading(isUploading, itemId) {
957
+            if (isUploading === true) {
958
+                spinner[itemId].classList.add("opacity-1");
959
+            } else {
960
+                spinner[itemId].classList.remove("opacity-1");
961
+            }
962
+        }
963
+        function getImageSize(file) {
964
+            return new Promise((resolve, reject) => {
965
+                var _URL = window.URL || window.webkitURL;
966
+                var img = new Image();
967
+                img.onload = () => resolve({ height: img.height, width: img.width });
968
+                img.onerror = reject;
969
+                img.src = _URL.createObjectURL(file);
970
+            });
971
+        }
972
+    </script>
973
+@endsection

+ 20
- 0
routes/web.php 파일 보기

@@ -7,6 +7,8 @@ use App\Http\Controllers\Backend\PasswordController;
7 7
 use App\Http\Controllers\Backend\DashboardController;
8 8
 use App\Http\Controllers\Backend\UsergroupsController;
9 9
 use App\Http\Controllers\Backend\FunmenusDetailController;
10
+use App\Http\Controllers\Backend\DataManagement\SignupManagementController;
11
+use App\Http\Controllers\Backend\DataManagement\TrackManagementController;
10 12
 
11 13
 // Fow AWS ELB 健康檢查
12 14
 Route::get('/health-check', function () {
@@ -75,5 +77,23 @@ Route::prefix('backend')->group(function () {
75 77
             Route::post('/{menuid}/store', 'store');
76 78
             Route::delete('/{menuid}/delete/{id}', 'delete');
77 79
         });
80
+        
81
+        // 報名資料查詢
82
+        Route::controller(SignupManagementController::class)->prefix('dataManagement/signup')->group(function () {
83
+            Route::get('/', 'index');
84
+            Route::get('/grid', 'grid');
85
+            Route::post('/export', 'export')->name('signup.export');
86
+        });
87
+
88
+        // 場次查詢
89
+        Route::controller(TrackManagementController::class)->prefix('dataManagement/track')->group(function () {
90
+            Route::get('/', 'index');
91
+            Route::get('/grid', 'grid');
92
+            Route::get('/create', 'create');
93
+            Route::get('/edit/{id}', 'edit');
94
+            Route::post('/store', 'store');
95
+            Route::delete('/{id}', 'delete');
96
+        });
97
+
78 98
     });
79 99
 });