hyeonga_code

reProject_40_Dropzone 이해하기, 관리자 상품 등록 이미지 업로드 적용, summernote S3 이미지 업로드 적용 본문

Project_WEATHERWEAR

reProject_40_Dropzone 이해하기, 관리자 상품 등록 이미지 업로드 적용, summernote S3 이미지 업로드 적용

hyeonga 2024. 2. 1. 05:59
반응형

 

reProject_39_위시리스트 추가/삭제 기능 작업

reProject_38_sweetAlert2 적용하기, myBatis foreach문으로 반복 update, delete 적용 2024.01.30 기존에 작업했던 파일중 foreach문이 실행되지 않아 DAO.java에서 for문으로 작업한 기능들을 수정 alert()을 custom하기위

hyeonga493.tistory.com

2024.01.31

상품 등록시 상품 사진 업로드, 리뷰 이미지, 공지 이미지 등 이미지를 aws s3에 업로드하는 기능을 이전에는 input file 태그로 작성했었는데 dropzone을 사용하면 편리하고 깔끔한 스타일로 이미지 미리보기 기능을 제공해준다고 하여 적용하려고 했다. 구글링을 해도 이해하기 어려운 부분이 많아 다른 팀원의 작업을 바탕으로 이해하고 정리하려고 작성

 

-- 파일

---- dropzone.js

---- dropzone.css

 

-- 영역 생성

<div class="dz-message needsclick">
    <span class="text" style="display:flex; align-items:center; flex-direction:column;">
        <img src="resources/util/image/dropzone_camera.png" alt="Camera" />
        <code>메인 이미지를 등록하세요</code><br>
    </span>
</div>

 

-- preview 생성 위치

---- <svg>태그는 괜찮은데 안의 <path> 태그는 적용이 안되는 것 같은데 어떻게 적용시키는지 알 수 없는 부분이다.

<div id="myTemplate" style="display: none;">
    <div id="mytmp" class="dz-preview dz-file-preview">
        <div class="dz-image"><img data-dz-thumbnail /></div>
        <div class="dz-details">
            <div class="dz-size"><span data-dz-size></span></div>
            <div class="dz-filename"><span data-dz-name></span></div>
        </div>
        <div class="dz-progress">
            <span class="dz-upload" data-dz-uploadprogress></span>
        </div>
        <div class="dz-error-message"><span data-dz-errormessage></span></div>
        <div class="dz-remove" data-dz-remove>
            <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
                <path d="M0 0h24v24H0z" fill="none" />
                <path d="M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
            </svg>
        </div>
    </div>
</div>

 

이 파일로 작업하려다가 <path> 태그가 눈에 밟히기도 하고 css가 적용이 안되어 다른 파일로 변경

<!-- 포스팅 - 이미지/동영상 dropzone 영역 -->
<div id="dropzone-preview" style="display:none;">
    <div class="dropzone-preview-list">
        <!-- This is used as the file preview template -->
        <div class="border rounded-3">
            <div class="d-flex align-items-center p-2">
                <div class="flex-shrink-0 me-3">
                    <div class="width-8 h-auto rounded-3">
                        <img data-dz-thumbnail="data-dz-thumbnail" class="w-full h-auto rounded-3 block" src="#" alt="Dropzone-Image" style="width: 120px;"/>
                    </div>
                </div>
                <div class="flex-grow-1" style="width: 300px;">
                    <div class="pt-1">
                        <h6 class="font-semibold mb-1" data-dz-name="data-dz-name">&nbsp;</h6>
                        <p class="text-sm text-muted fw-normal" data-dz-size="data-dz-size"></p>
                        <strong class="error text-danger" data-dz-errormessage="data-dz-errormessage"></strong>
                    </div>
                </div>
                <div class="shrink-0 ms-3">
                    <button data-dz-remove="data-dz-remove" class="btn btn-sm btn-danger">삭제</button>
                </div>
            </div>
            <script>
                //document.querySelector("#mainImage").style.display = "none";
            </script>
        </div>
    </div>
</div>

 

 

dropzone.js

/**
 * 
 */
 $(document).ready(function() {
 	// 서버에 전송할 파일과 관련된 추가 정보
	var hiddenkey = $('#key').val();

 	var dropzoneError = '';
 	Dropzone.autoDiscover = false;
 	const dropzone = new Dropzone('div#dropzone', {
 		url: 'fileUpload.mdo',	// 파일 업로드 로직 호출
 		method: 'post',	
		autoProcessQueue: false,		/** 자동 업로드 */
		autoQueue: true,			/** 파일 업로드시 큐에 자동 업로드 */ 
		clickable: true,			/** 클릭 가능 여부 */
		createImageThumbnails: true,		/** 이미지 미리보기 */
		thumbnailHeight: 150,			/** 이미지 크기 조절 */
		thumbnailWidth: 150,			/** 이미지 크기 조절 */
		paramName: 'images',			/** 서버로 전송될 파일의 파라미터 이름 */
		params: { key: hiddenkey },		/** 파일과 전송될 추가 정보 */
		// maxFiles : 업로드하는 파일 수
		// maxFilesize: 최대 업로드용량(MB)
		// timeout: 커넥션 타임아웃 설정
		addRemoveLinkes: true,			/** 삭제 버튼 표시 여부 */
		dicRemoveFile: 'X',			/** 삭제 버튼 텍스트 */
		parallelUploads: 10,			/** 동시 업로드가능한 파일 수 */
		uploadMultiple: true,			/** 다중 업로드 기능 */
		acceptedFiles: '.jpeg,.jpg,.png,.gif,.JPEG,.JPG,.PNG,.GIF',
		dictInvalidFileType: '이미지 파일만 업로드 가능합니다.',
 		previewTemplate: document.querySelector('#myTemplate').innerHTML,
 
 		/** 초기 설정 */
 		init: function() {
			var myDropzone = this;
			
			// 업로드 대기중인 파일 처리
			$('.insertBtn').on('click', function(e) {
				myDropzone.processQueue();	
			});
			
			// 업로드 대기중/업로드된 파일 모두 삭제
			$('.cancelBtn').on('click', function(e) {
				myDropzone.removeAllFiles(true);	
			});
			
			// maxfilesexceeded : 업로드 개수를 초과하는 경우
			this.on('maxfilesexceeded', function(file) {
				playToast('최대 10개까지 업로드 하실 수 있습니다.', 'info');
				myDropzone.removeFile(file);
			});

			// sendingmultiple : 여러 파일을 동시에 업로드시, 첫 전송 직전에 발생
			this.on('sendingmultiple', function(file, xhr, formData) {
				playToast('sending', 'info');
				console.log('보내는 중');
			});
			
			// successmultiple : 다중 파일 업로드 성공한 경우
			this.on('successmultiple', function(file, responseText) {
				playToast('Success to Upload', 'success');
			});
			
			this.on('error', function(file, errorMessage) {
				playToast('Error to Upload', 'error');
				console.log(errorMessage);
			});
			
			// queuecomplete : 이벤트 성공 여부 확인 로그
			this.on('queuecomplete', function(e) {
				console.log('queuecomplete');
			});
			
			// 업로드된 파일 삭제
			this.on('removedfile', function(data) {
				playToast('Remove File', 'info');
			});
		}
 	});
});

 

파일을 따로 작업하려고 했는데 $(document).ready() 코드를 포함한 js 파일이 분리되어 있어 dropzone.param.imageBy값을 변경할 수 없어 코드를 합쳐 작업. 상품 등록 화면에서 상품 카테고리를 변경하는 경우 바로 productId에 반영이 되어야 하므로 이렇게 작업했다. 상품을 등록하는 과정과 함꼐 이미지를 업로드하고 image 테이블에 정보를 저장하려니 조금 더 복잡해진 것 같다.

 상세 이미지는 summernote에 바로 적용되어 코드로 박혀들어가게 작업이 되어 있어 따로 테이블에 추가하지 않아도 될 듯 싶다. 추후 필요한 부분이 있다면 수정하려고 한다.

 

1) productRegister.jsp수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WeatherWear 관리자</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
 
<!-- Font Awesome -->
<link href="resources/admin/AdminLTE/plugins/fontawesome-free/css/all.min.css" rel="stylesheet">
<!-- Theme style -->
<link href="resources/admin/AdminLTE/dist/css/adminlte.min.css" rel="stylesheet">
<!-- dropZone -->
<link rel="stylesheet" href="resources/util/plugins/dropzone/dist/dropzone.min.css"/>
<link rel="stylesheet" href="resources/util/plugins/dropzone/custom.css">
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
<style>
    #option-div { display: none;}
</style>
</head>
<body class="hold-transition sidebar-collapse layout-top-nav">
    <div class="wrapper">
        <%@ include file="../header.jsp" %>
    
        <div class="content-header">
            <section class="content-header">
                <div class="container">
                    <div class="row mb-2">
                        <div class="col-sm-6">
                            <h1>상품 등록</h1>
                        </div>
                        <div class="col-sm-6">
                            <ol class="breadcrumb float-sm-right">
                                <li class="breadcrumb-item"><a href="main.mdo">메인</a></li>
                                <li class="breadcrumb-item active">상품 등록</li>
                            </ol>
                        </div>
                    </div>
                </div>
            </section>
            <section class="content">
                <div class="container">
                    <div class="row">
                        <div class="card card-primary" style="width:48%;">
                            <div class="card-header">
                                <h3 class="card-title">Product</h3>
                            </div>
                            <!-- /.card-header -->
                            <div class="card-body">
                                <h4>Information</h4>
                                <div id="dropzone">
                                        <input type="hidden" id="key" name="key" value="product">
                                        <input type="hidden" id="imageStatus" name="imageStatus" value="대표">
                                        <input type="hidden" id="lastId" name="lastId" value="${ productId }">
                                        <input type="hidden" id="productId" name="productId" value="W2OT${ productId }">
                                        <input type="hidden" id="imageBy" name="imageBy" value="W2OT${ productId }">
                                        <div id="mainImage" class="dz-message needsclick">
                                            <span class="text" style="display:flex; align-items:center; flex-direction:column;">
                                                <img src="resources/util/image/dropzone_camera.png" alt="Camera" />
                                                <code>메인 이미지를 등록하세요</code><br>
                                            </span>
                                            <span class="plus">+</span>
                                        </div>
 
                                <!-- 포스팅 - 이미지/동영상 dropzone 영역 -->
                                    <div id="dropzone-preview" style="display:none;">
                                        <div class="dropzone-preview-list">
                                            <!-- This is used as the file preview template -->
                                            <div class="border rounded-3">
                                                <div class="d-flex align-items-center p-2">
                                                    <div class="flex-shrink-0 me-3">
                                                        <div class="width-8 h-auto rounded-3">
                                                            <img data-dz-thumbnail="data-dz-thumbnail" class="w-full h-auto rounded-3 block" src="#" alt="Dropzone-Image" style="width: 120px;"/>
                                                        </div>
                                                    </div>
                                                    <div class="flex-grow-1" style="width: 300px; padding-left:10px;">
                                                        <div class="pt-1">
                                                            <h6 class="font-semibold mb-1" data-dz-name="data-dz-name">&nbsp;</h6>
                                                            <p class="text-sm text-muted fw-normal" data-dz-size="data-dz-size"></p>
                                                            <strong class="error text-danger" data-dz-errormessage="data-dz-errormessage"></strong>
                                                        </div>
                                                    </div>
                                                    <div class="shrink-0 ms-3">
                                                        <button data-dz-remove="data-dz-remove" class="btn btn-sm btn-danger">삭제</button>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div class="form-group">
                                    <label for="exampleSelectBorder">카테고리</label>
                                    <select class="custom-select form-control-border" id="exampleSelectBorder" onchange="setProductId()">
                                        <option value="11">OUTER</option>
                                        <option value="12">TOP</option>
                                        <option value="13">PANTS</option>
                                        <option value="14">SKIRTS</option>
                                        <option value="15">DRESS</option>
                                    </select>
                                </div>
                                <div class="form-group">
                                    <label for="exampleInputBorderWidth2"> 상품명<code></code></label>
                                    <input type="text" name="productName" class="form-control form-control-border border-width-2" id="exampleInputBorderWidth2" placeholder="상품명">
                                </div>
                                <div class="form-group">
                                    <label for="exampleInputBorderWidth2"> 공급가<code> 과세율 0.1, 마진율 1.5 >> 판매가 : <span id="cost"></span></code></label>
                                    <input type="text" name="productPrimeCost" class="form-control form-control-border border-width-2" id="exampleInputBorderWidth2" placeholder="공급가" onchange="checkCost()">
                                </div>
                                <h4>Option Setting</h4>
                                <div class="form-group">
                                    <label for="exampleInputRounded0">색상<code></code></label>
                                    <input type="text" class="form-control rounded-0" id="exampleInputRounded0" name="optionColor" placeholder="블랙, 화이트">
                                    <input type="hidden" name="optionColorList">
                                </div>
                                <div class="form-group">
                                    <label for="exampleInputRounded0">사이즈<code></code></label>
                                    <input type="text" class="form-control rounded-0" id="exampleInputRounded0" name="optionSize" placeholder="S, M, L">
                                    <input type="hidden" name="optionSizeList">
                                </div>
                                <button type="button" class="btn btn-block btn-outline-primary" id="apply-option">옵션적용</button>
                            </div>
                            <!-- /.card-body -->
                        </div>
                        <div style="width:20px;"></div>
                        <div class="card card-secondary" style="width:48%;" id="option-div">
                            <div class="card-header">
                                <h3 class="card-title">Option/Stock</h3>
                            </div>
                            <!-- /.card-header -->
                            <div class="card-body">
                                <h4>Stock</h4>
                                <div class="card-body p-0" style="min-height: 600px; height:auto;">
                                    <table class="table table-sm">
                                        <colgroup>
                                            <col width="100px"/>
                                            <col width="100px"/>
                                            <col width="100px"/>
                                        </colgroup>
                                        <thead>
                                            <tr>
                                                <th>색상</th>
                                                <th>사이즈</th>
                                                <th>수량</th>
                                            </tr>
                                        </thead>
                                        <tbody class="option-stock">
                                            
                                        </tbody>
                                    </table>
                                </div>
                                <div class="input-group mb-3">
                                    <!-- /btn-group -->
                                    <input type="number" class="form-control" value="10" name="allStock">
                                    <input type="hidden" class="form-control" name="stockList">
                                    <div class="input-group-prepend">
                                        <button type="button" class="btn btn-primary" id="applyStockAll">재고 일괄 적용</button>
                                    </div>
                                </div>
                            </div>
                            <!-- /.card-body -->
                        </div>
                    </div>
                    <div class="card card-outline card-primary">
                        <div class="card-header">
                            <h3 class="card-title">상세 내용</h3>
                        </div>
                        <!-- /.card-header -->
                        <div class="card-body">
                            <div id="summernote"></div>
                        </div>
                        <!-- /.card-body -->
                    </div>
                    <div class="input-group-prepend">
                        <button type="button" class="btn btn-primary insertBtn" onclick="submit('register')">등록하기</button>
                    </div>
                </div>
            </section>
        </div>        
        <%@ include file="../footer.jsp" %>
    </div>
<!-- jQuery -->
<script src="resources/admin/AdminLTE/plugins/jquery/jquery.min.js"></script>
<script    src="resources/util/plugins/sweetalert/jquery-lates.min.js"></script>
<script src="resources/util/plugins/sweetalert/sweetalert2.js"></script>
<!-- jQuery UI 1.11.4 -->
<script src="resources/admin/AdminLTE/plugins/jquery-ui/jquery-ui.min.js"></script>
<!-- Bootstrap 4 -->
<script src="resources/admin/AdminLTE/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Summernote -->
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
<!-- dropzone -->
<script src="resources/util/plugins/dropzone/dist/dropzone.min.js"></script>
<!-- sweetAlert (alert/confirm/toast) -->
<script src="resources/util/js/sweetalert.js"></script>
 
<script src="resources/admin/js/manageProduct.js"></script>
</body>
</html>

 

 

2) manageProduct.js 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/**
 * 상품 옵션/재고 적용
 */
 $(document).ready(function(){
     // summernote 실행
    $('#summernote').summernote({
        height: 300
    });
 
    const optionDiv = document.querySelector("#option-div");
    
    let optionColorList = applyOption('optionColor');
    let optionSizeList = applyOption('optionSize');
        
    // 옵션 적용
    $("#apply-option").click(function(){
    
        optionColorList = applyOption('optionColor');
        optionSizeList = applyOption('optionSize');
        if(optionColorList == "" || optionSizeList == ""){
            return;
        }
        
        optionDiv.style.display = 'flex';
        $(".option-stock").html("");    
        
        for(var i=0; i<optionColorList.length; i++){
            for(var j=0; j<optionSizeList.length; j++){
                $(".option-stock").append("<tr><td>" + optionColorList[i] +"</td><td>" + optionSizeList[j] + "</td><td>"
                             + "<input type='number' class='custom-select form-control-border' name='stCnt' id='stCnt" 
                             + optionColorList[i] + optionSizeList[j] + "' value='0'></td></tr>");
            }
        }
    });
    
    // 재고 일괄 적용
    $("#applyStockAll").click(function(){
        var stCntList = [];
        var cnt = $("input[name='allStock']").val();
        
        for(var i=0; i<optionColorList.length; i++){
            for(var j=0; j<optionSizeList.length; j++){
                
                stCntList.push(cnt);
                $("#stCnt" + optionColorList[i] + optionSizeList[j]).val(cnt);
            }
        }
        setStock(stCntList);
    });
    
    function setProductId() {
        // 선택된 option의 value 가져오기
        let productCate =  document.querySelector("#exampleSelectBorder").value;
        let productId = 'W2';
        
        switch(productCate){
        case "11":
            productId += 'OT'break;
        case "12":
            productId += 'TS'break;
        case "13":
            productId += 'PT'break;
        case "14":
            productId += 'SK'break;
        case "15":
            productId += 'DR'break;
        default :
            productId += 'AL'break;
        }
        productId += $("#lastId").val();
        $("input[name='productId']").val(productId);
        $("input[name='imageBy']").val(productId);
        
        // dropzone의 imageBy 값 갱신
        dropzone.options.params.imageBy = productId;
        console.log("dropzone.options.params.imageBy : " + dropzone.options.params.imageBy);
    }
    
     // 서버에 전송할 파일과 관련된 추가 정보
    var hiddenkey = $('#key').val();
    var imageStatus = $('#imageStatus').val();
    var imageBy = $("#imageBy").val();
    
     var dropzoneError = '';
     Dropzone.autoDiscover = false;
     const dropzone = new Dropzone('div#dropzone', {
         url: 'fileUpload.mdo',    // 파일 업로드 로직 호출
         method: 'post',    
        autoProcessQueue: false,        /** 자동 업로드 */
        autoQueue: true,                /** 파일 업로드시 큐에 자동 업로드 */ 
        clickable: true,                /** 클릭 가능 여부 */
        createImageThumbnails: true,    /** 이미지 미리보기 */
        thumbnailHeight: 150,            /** 이미지 크기 조절 */
        thumbnailWidth: 150,            /** 이미지 크기 조절 */
        paramName: 'images',            /** 서버로 전송될 파일의 파라미터 이름 */
        params: {                         /** 파일과 전송될 추가 정보 */
            key: hiddenkey,
            imageStatus: imageStatus,
            imageBy: imageBy
         },
        addRemoveLinkes: true,            /** 삭제 버튼 표시 여부 */
        dicRemoveFile: 'X',                /** 삭제 버튼 텍스트 */
        parallelUploads: 10,            /** 동시 업로드가능한 파일 수 */
        uploadMultiple: true,            /** 다중 업로드 기능 */
        acceptedFiles: '.jpeg,.jpg,.png,.gif,.JPEG,.JPG,.PNG,.GIF',
        dictInvalidFileType: '이미지 파일만 업로드 가능합니다.',
         previewTemplate: document.querySelector('#dropzone-preview').innerHTML,
 
         /** 초기 설정 */
         init: function() {
            var myDropzone = this;
            
            // 업로드 대기중인 파일 처리
            $('.insertBtn').on('click'function(e) {
                myDropzone.processQueue();    
            });
            
            // 업로드 대기중/업로드된 파일 모두 삭제
            $('.cancelBtn').on('click'function(e) {
                playToast('Delete Image''info');
                myDropzone.removeAllFiles(true);    
            });
            
            // maxfilesexceeded : 업로드 개수를 초과하는 경우
            this.on('maxfilesexceeded'function(file) {
                playToast('최대 10개까지 업로드 하실 수 있습니다.''info');
                myDropzone.removeFile(file);
            });
 
            // sendingmultiple : 여러 파일을 동시에 업로드시, 첫 전송 직전에 발생
            this.on('sendingmultiple'function(file, xhr, formData) {
                playToast('sending''info');
                console.log('보내는 중');
            });
            
            // successmultiple : 다중 파일 업로드 성공한 경우
            this.on('successmultiple'function(file, responseText) {
                playToast('Success to Upload''success');
            });
            
            this.on('error'function(file, errorMessage) {
                playToast('Error to Upload''error');
                console.log(errorMessage);
            });
            
            // queuecomplete : 이벤트 성공 여부 확인 로그
            this.on('queuecomplete'function(e) {
                console.log('queuecomplete');
            });
            
            // 업로드된 파일 삭제
            this.on('removedfile'function(data) {
                console.log("data : " + data.name);
                playToast('Remove File''success');
            });
        }
     });
});
 
// 재고 적용
function setStock(stCntList){
    $("input[name='stockList']").val(stCntList);
}
 
// 옵션 적용
function applyOption(name){
    var optionList = $("input[name='" + name + "']").val().replace(/\s/g, '');
    var list = [];
 
    if(optionList){
        optionList.split(",").forEach(function(option){
            if(list.includes(option)){
                alert("동일한 값을 적용할 수 없습니다.");
                $("input[name='" + name + "']").val("");
                document.querySelector("input[name='" + name + "']").focus();
                list = [];
                return null;
            }
            if(option != ""){
                list.push(option);
            }
        });
    }
    
    $("input[name='" + name + "List']").val(list);
    
    return list;
}
 
// 재고 적용
function applyStock(optionColorList, optionSizeList){
    var stCntList = [];
    
    for(var i=0; i<optionColorList.length; i++){
        for(var j=0; j<optionSizeList.length; j++){
            console.log("cnt : " + $("#stCnt" + optionColorList[i] + optionSizeList[j]).val());
            stCntList.push($("#stCnt" + optionColorList[i] + optionSizeList[j]).val());
        }
    }
    setStock(stCntList);
}
 
// 판매가 확인
function checkCost(){
    let price = parseInt($("input[name='productPrimeCost']").val());
    let cost = Math.ceil((price + (price*0.1)*2.5)/100)*100;
    document.querySelector("#cost").innerHTML = cost;
}
 
// 적용
function submit(type){
    let colorList = applyOption('optionColor');
    let sizeList = applyOption('optionSize');
    let url_type = '';
    let productId = $("input[name='productId']").val();
 
    applyStock(colorList, sizeList);
    
    // 카테고리
    let productCate = document.querySelector("#exampleSelectBorder").value;
    let productName = $("input[name='productName']").val();
    let productContent = $('#summernote').summernote('code');
    let productPrimeCost = $("input[name='productPrimeCost']").val();
    let stockCntList = $("input[name='stockList']").val();
    let optionColorList = $("input[name='optionColorList']").val();
    let optionSizeList = $("input[name='optionSizeList']").val();
    
    if(type == 'register'){
        url_type = 'productRegisterProc.mdo';
    } else if(type == 'modify'){
        url_type = 'productUpdateProc.mdo';
        productId = $("input[name='productId']").val();
    }
    
    $.ajax({
        type: "post",
        url: url_type,
        dataType: "json",
        data: {
            productId: productId,
            productCate: productCate,
            productName: productName,
            productContent: productContent,
            productPrimeCost: productPrimeCost,
            colorList: optionColorList,
            sizeList: optionSizeList,
            cntList: stockCntList,
        },
        success: function(res) {
            if(res.code == -1) {
                alert(res.message);
                return;
            } 
            
            if(res.code == 1) {
                if(confirm(res.message)){
                    if(type == 'register'){
                        location.href = 'productInfo.mdo?productId=' + res.data.productId;
                    } else {
                        location.href = 'productInfo.mdo?productId=' + productId;
                    }
                } else {
                    location.href = 'productList.mdo';
                }
            }
        },
        error: function(request, status, error) {
            console.log("code: " + request.status + "\n" + "message: " + request.responseText + "\n" + "error: " + error);
        }
    })
}
 
// 상품 삭제
function deleteProduct(){
    if(confirm("상품을 삭제하시겠습니까?")){
        const params = new URLSearchParams(location.search);
        let productId = params.get('productId');
 
        $.ajax({
            type: "post",
            url: "productDelete.mdo",
            dataType: "json",
            data: {
                productId: productId,
            },
            success: function(res) {
                if(res.code == -1) {
                    alert(res.message);
                    return;
                } 
                
                if(res.code == 1) {
                    alert(res.message);
                    location.href="productList.mdo";
                }
            },
            error: function(request, status, error) {
                console.log("code: " + request.status + "\n" + "message: " + request.responseText + "\n" + "error: " + error);
            }
        })
    }
}
 
// 상품 상태 변경
function update(){
    let checkList = [];
    let selectedList = document.querySelectorAll("input[type='checkbox']:checked");
    let productSell = $("#productSell_value").val();
    
    if(selectedList.length < 1){
        alert("변경할 데이터를 선택해주세요");
        return;
    }
    
    for(let i=0; i<selectedList.length; i++){ 
        if(!checkList.includes(selectedList[i].value)){
            let product = {};
            
            product.productSell = productSell;
            product.productId = selectedList[i].value;
            checkList.push(product);
        }
    }
    
    $.ajax({
        url: "/w2/productUpdateStatus.mdo",
        type: "POST",
        data: JSON.stringify(checkList),
        dataType: "json",
        contentType: "application/json",
        success: function(){
            alert("수정되었습니다.");
            window.location.reload();
        },
        error: function(){
            console.log("실패");
        }
    });
}

 

 

3) FileController.java 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package com.w2.file.controller;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
 
import com.w2.file.AwsS3;
import com.w2.file.ImageVO;
import com.w2.file.service.ImageService;
import com.w2.util.RandomString;
import com.w2.util.ResponseDTO;
 
import lombok.RequiredArgsConstructor;
 
@RestController
@RequiredArgsConstructor
public class FileController {
    
    @Autowired
    private AwsS3 awsS3;
 
    @Autowired
    private ImageService imageService;
    
    /** 
     * Dropzone(상품 등록)
     * @param request
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "fileUpload.mdo")
    public List<ImageVO> fileUpload(MultipartHttpServletRequest request) throws Exception {
        String key = request.getParameter("key");
        String imageStatus = request.getParameter("imageStatus");
        String imageBy = request.getParameter("imageBy");
        
        Map<String, MultipartFile> fileMap = request.getFileMap();
        List<ImageVO> imageList = new ArrayList<ImageVO>();
        
        for(MultipartFile multipartFile: fileMap.values()) {
            String resultUrl = awsS3.upload(multipartFile, key+"_image");
            String s3Path = resultUrl.substring(0, resultUrl.indexOf(key) + (key + "_image").length() + 1);
            String s3FileName = resultUrl.substring(resultUrl.indexOf(key) + (key + "_image").length() + 1, resultUrl.length());
            
            ImageVO image = new ImageVO();
            image.setImageBy(imageBy);
            image.setImageStatus(imageStatus);
            image.setImageDir(s3Path);
            if(key.equals("product")) {    // 상품 대표 이미지 삭제
                imageService.deleteProductImage(image);
                image.setImageId(imageService.checkImage(image));
            }
            image.setImageId(s3FileName.split("\\.")[0]);
            image.setImageName(s3FileName);
            imageList.add(image);
        }
 
        Map<String, Object> imageMap = new HashMap<String, Object>();
        imageMap.put("list", imageList);
        imageMap.put("key", key);
 
        int result = imageService.insertImage(imageMap);
        
        return imageList;
    }
    
    /**
     * summernote 이미지 파일 s3 server 업로드
     * @param file: 이미지 파일 정보
     * @param key: 업로드 폴더 정보
     * @return
     * @throws IOException
     */
    @ResponseBody
    @RequestMapping("summernoteFileUpload.mdo")
    public String summernoteFileUpload(MultipartFile file, String key) throws IOException {
        
        if(key.equals("notice")) {
            key = "notice_image";
        }
            
        return awsS3.upload(file, key);
    }
    
    /**
     * summernote 이미지 파일 s3 server 삭제
     * @param file: 삭제할 파일 경로
     */
    @ResponseBody
    @RequestMapping("summernoteFileDelete.mdo")
    public void summernoteFileDelete(String file) {
        awsS3.delete(file.substring(file.indexOf("/"10+ 1));
    }
 
    /**
     * 이미지 테이블 업로드
     * @param request
     * @param type
     * @param image
     * @return
     */
    @ResponseBody
    @RequestMapping("imageInsert.mdo")
    public ResponseDTO<String>  imageInsert(HttpServletRequest request, @RequestBody List<ImageVO> imageList) {
        Integer statusCode = HttpStatus.OK.value();
        int code=1;
        String resultCode="";
        String msg="";
        
        Map<String, Object> imageMap = new HashMap<String, Object>();
        imageMap.put("list", imageList);
        imageMap.put("key", request.getParameter("key"));
 
        try {
            int result = imageService.insertImage(imageMap);
 
            if(result > 0) {
                code = 1;
                resultCode = "success";
                msg = "이미지 등록 성공";
            } else {
                code = -1;
                resultCode = "fail";
                msg = "이미지 등록 실패";
            }
        } catch (Exception e) {
            e.printStackTrace();
            code = -1;
            resultCode = "fail";
            msg = "오류가 발생했습니다. 다시 시도해주세요";
        }
        
        return new ResponseDTO<String>(statusCode, code, resultCode, msg, null);
    }
}
 

 

 

4) ImageVO.java 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.w2.file;
 
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
 
@Setter
@Getter
@ToString
public class ImageVO {
    private String ImageId;        // 이미지번호
    private String ImageName;    // 이미지이름(이미지번호 + 확장자)
    private String ImageDir;    // 이미지경로
    private String ImageStatus;
        /*
         * product: 대표, 상세, (추가)
         * admin : 공지, 메인, 기타 
         * client : 문의, 리뷰, 환불
         */
    private String ImageBy;        
        /*
         * product: productId
         * admin: adminId
         * client: clientId
         */
}
 

 

 

5) ImageService.java 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.w2.file.service;
 
import java.util.Map;
 
import com.w2.file.ImageVO;
 
public interface ImageService {
 
    int insertImage(Map<String, Object> imageMap);    // 이미지 테이블 등록
    String checkImage(ImageVO image);    // 대표 이미지 존재 여부 확인
    void deleteProductImage(ImageVO image);
}
 

 

 

6) ImageServiceImpl.java 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.w2.file.service;
 
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.w2.file.ImageDAO;
import com.w2.file.ImageVO;
 
@Service
public class ImageServiceImpl implements ImageService {
    @Autowired
    private ImageDAO imageDAO;
 
    @Override
    public int insertImage(Map<String, Object> imageMap) {
        return imageDAO.insertImage(imageMap);
    }
 
    @Override
    public String checkImage(ImageVO image) {
        System.err.println("checkImage image : " + image);
        return imageDAO.checkImage(image);
    }
 
    @Override
    public void deleteProductImage(ImageVO image) {
        imageDAO.deleteProductImage(image);
    }
}
 

 

 

7) ImageDAO.java 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.w2.file;
 
import java.util.Map;
 
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
@Repository
public class ImageDAO {
    @Autowired
    SqlSessionTemplate sqlSessionTemplate;
 
    public int insertImage(Map<String, Object> imageMap) {
        return sqlSessionTemplate.insert("ImageDAO.insertImage", imageMap);
    }
 
    public String checkImage(ImageVO image) {
        return sqlSessionTemplate.selectOne("ImageDAO.checkImage", image);
    }
 
    public void deleteProductImage(ImageVO image) {
        sqlSessionTemplate.delete("ImageDAO.deleteProductImage", image);
    }
    
}
 

 

 

8) file-mapping.xml 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
                 
<mapper namespace="ImageDAO">
    
    <sql id="setTable">
        <choose>
            <when test="key == 'product'">product_image</when>
            <when test="key == 'notice'">admin_image</when>
            <when test="key == 'main'">admin_image</when>
            <when test="key == 'review'">client_image</when>
            <when test="key == 'qna'">client_image</when>
            <when test="key == 'refund'">client_image</when>
            <otherwise>admin_image</otherwise>
        </choose>
    </sql>
    
    <insert id="insertImage" parameterType="hashMap">
        <foreach collection="list" item="image">
        INSERT INTO <include refid="setTable"/>(imageId, imageName, imageDir, imageBy, imageStatus)
        VALUES (#{ image.imageId }, #{ image.imageName }, #{ image.imageDir }, #{ image.imageBy }, #{ image.imageStatus })
        </foreach>
    </insert>
    
    <select id="checkImage" parameterType="image" resultType="String">
        SELECT imageId 
        FROM product_image
        WHERE imageBy=#{ imageBy }
        <if test="imageStatus =='대표'">
            AND imageStatus='대표'
        </if>
    </select>
    
    <delete id="deleteProductImage" parameterType="image">
        DELETE FROM product_image
        WHERE imageBy = #{ imageBy } AND imageStatus = #{ imageStatus }
    </delete>
</mapper>

 

 

 

9) mybatis-config,xml 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
 
<typeAliases>
<!-- typeAlias
        - 매핑파일에서 사용하는 type을 지정
- 애플리케이션에서 SQL 문으로 값을 전달합니다
- SQL 문 실행 시 반환되는 레코드를 저장하는 용도로 사용하기 위한 빈을 생성합니다.-->
 
    <typeAlias type="com.w2.admin.AdminVO" alias="admin"/>
    <typeAlias type="com.w2.client.ClientVO" alias="client"/>
    <typeAlias type="com.w2.board.NoticeVO" alias="notice"/>
    <typeAlias type="com.w2.board.QnaVO" alias="qna"/>
    <typeAlias type="com.w2.board.TermsVO" alias="terms"/>
    <typeAlias type="com.w2.product.ProductVO" alias="product"/>
    <typeAlias type="com.w2.product.ProductPriceVO" alias="productPrice"/>
    <typeAlias type="com.w2.product.OptionVO" alias="option"/>
    <typeAlias type="com.w2.order.OrderVO" alias="order"/>
    <typeAlias type="com.w2.delivery.DeliveryVO" alias="delivery"/>
    
    <typeAlias type="com.w2.cart.CartVO" alias="cart"/>
    <typeAlias type="com.w2.file.ImageVO" alias="image"/>
</typeAliases>
 
<!-- SQL 작성문을 지정하여 mapper 파일 경로 알려주는 역할입니다. -->
<mappers>
    <mapper resource="mappings/admin-mapping.xml"/>
    <mapper resource="mappings/notice-mapping.xml"/>
    <mapper resource="mappings/client-mapping.xml"/>
    <mapper resource="mappings/qna-mapping.xml"/>
    <mapper resource="mappings/terms-mapping.xml"/>
    <mapper resource="mappings/product-mapping.xml"/>
    <mapper resource="mappings/order-mapping.xml"/>
    <mapper resource="mappings/delivery-mapping.xml"/>
    
    <mapper resource="mappings/cart-mapping.xml"/>
    <mapper resource="mappings/file-mapping.xml"/>
</mappers>
</configuration>

 

 

 

10) summernote.js 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * 
 */
 $(document).ready(function() {
     var key = $('#key').val();
     let list = [];
     
     // 이미지 테이블 등록
     $("#checkBtn").click(function(){
         let imageStatus = $("#imageStatus").val();
         let imageBy = $("#imageBy").val();
         imageInsert(key, list, imageBy, imageStatus);
     });
     
     $('#summernote').summernote({
         height: 500,
         minHeight: null,
         maxHeight: null,
         focus: false,
         lang: 'ko-KR',
         toolbar: [
                     ['fontname', ['fontname']],
                     ['fontsize', ['fontsize']],
                     ['style', ['bold''italic''underline''strikethrough''clear']],
                     ['color', ['forecolor''color']],
                     ['table', ['table']],
                     ['para', ['ul''ol''paragraph']],
                     ['height', ['height']],
                     ['insert', ['link''picture']],
                     ['view', ['help']]
                 ],
         fontNames: ['Arial''Arial Black''Comic Sans MS''Courier New''맑은 고딕''궁서''굴림체''굴림''돋움''돋움체''바탕체'],
         fontSize: ['8''9''10''11''12''14''16''18''20''22''24''28''30''36''50''72'],
         callbacks: {
             // 이미지 업로드
             onImageUpload: function(files, editor) {
                 for(var i = files.length - 1; i >= 0; i--) {
                     sendFile(files[i], this);
                 }
             },
             
             // 이미지 삭제
             onMediaDelete: function(target) {
                 deleteFile(target[0].src);
             }
         },
     });
     
     function sendFile(file, el) {
         var formData = new FormData();
         formData.append('file', file);
         formData.append('key', key);
         $.ajax({
             url: "summernoteFileUpload.mdo",
             type: "post",
             data: formData,
             cache: false,
             contentType: false,
             enctype: "multipart/form-data",
             processData: false,
             success: function(imagePath) {
                 $(el).summernote('editor.insertImage', imagePath);
                 list.push(imagePath);
             }
         });
     }
     
     function deleteFile(fileSrc) {
         var formData = new FormData();
         formData.append('file', file);
        $.ajax({
             url: "summernoteFileDelete.mdo",
             type: "post",
             data: formData,
             cache: false,
             contentType: false,
             enctype: 'multipart/form-data',
             processData: false,
         });
     }
     
     var reset = function() {
         $('#summernote').reset();
     }
     
     function imageInsert(key, list, imageBy, imageStatus){
         let imList = [];
         list.forEach(function(imagePath){
             let image = {};
             
             if(imageStatus == null || imageStatus == ''){
                 switch(key){
                 case 'product': image.imageStatus = '상세'break;
                 case 'notice': image.imageStatus = '공지'break;
                 case 'main': image.imageStatus = '메인'break;
                 case 'review': image.imageStatus = '리뷰'break;
                 case 'qna': image.imageStatus = '문의'break;
                 case 'refund': image.imageStatus = '환불'break;
                 default: image.imageStatus = '기타'break;
                 }
             }else {
                 image.imageStatus = imageStatus;
             }
             image.imageBy = imageBy;
             image.imageDir = imagePath.split("_image/")[0]+"_image/";
             image.imageName = imagePath.split("_image/")[1];
             image.imageId = (imagePath.split("_image/")[1]).split(".")[0];
             imList.push(image);
         });
         
        $.ajax({
            url: "/w2/imageInsert.mdo?key=" + key,
            type: "POST",
            async: true,
            data: JSON.stringify(imList),
            contentType: "application/json",
            success: function(res){
                alert("success imageInsert");
            },
            error : function(error){
                alert("fail imageInsert");
            }
        });
     }
 });

 

>>> 실행 

 

>>> 메인 이미지 등록

-- 삭제 버튼을 클릭하면 업로드 목록에서 삭제됨 

 

>>> summernote 이미지 등록

 

>>> 전체 화면

 

>>> 등록하기 > 상품 상세

 

 

>> 상품 상세 정보 변경

 

>>> 변경 후

 

 

reProject_41_사용자 주문하기 페이지, 스크립트 작성

reProject_40_Dropzone 이해하기, 관리자 상품 등록 이미지 업로드 적용, summernote S3 이미지 업로드 적용 2024.01.31 상품 등록시 상품 사진 업로드, 리뷰 이미지, 공지 이미지 등 이미지를 aws s3에 업로드하

hyeonga493.tistory.com

 

반응형