상품등록 구현

WAS·2023년 7월 2일
0

세미프로젝트

목록 보기
10/11

✅ 상품등록


  • 상품등록의 UI는 다음과 같이 구현하였다

위의 UI 부분에서 가장 어려웠던 사진등록 UI부분 + 카테고리 + 검색태그등록 UI + 가격 정규표현식에 대해서만 작성해보겠다

  • 이미지 안에 input type file을 넣고 그 부분을 안보이게 설정한 작업
  • 이미지를 클릭시 -> 파일을 선택할 수 있는 UI
<img src="<%=request.getContextPath()%>/images/productregist/imgregist.png" 
class="upload" width="250px" height="250px"> 
<input type="file" id="inputFile" class="real-upload" 
accept="image/*" required multiple style="display: none;"> 
<!-- multiple -> 사용자가 둘 이상의 값을 입력할 수 있음을 명시 -->
<!--  email, file 타입에만 적용 가능함 -->
// 사진 불러오기 작업 

let prouductImgCnt = 0;
const dataTransfer = new DataTransfer();


function getImageFiles(e) {
	const files = e.currentTarget.files;
	const imagePreview = document.querySelector('.image-preview');

	const newFiles = [...files];
	let currentCount = dataTransfer.items.length;

	for (let file of newFiles) {

		// 파일 타입 검사
		if (!file.type.match("image/.*")) {
			alert('이미지 파일만 업로드가 가능합니다.');
			continue;
		}

		// 중복 검사: 이미 dataTransfer에 같은 파일 있는지 확인
		let isDuplicate = false;
		for (let i = 0; i < dataTransfer.items.length; i++) {
			const existingFile = dataTransfer.items[i].getAsFile();
			if (
				existingFile.name === file.name &&
				existingFile.lastModified === file.lastModified &&
				existingFile.size === file.size
			) {
				isDuplicate = true;
				break;
			}
		}

		if (isDuplicate) {
			alert(`이미 업로드한 이미지입니다: ${file.name}`);
			continue;
		}

		// 총 이미지 수 검사 (현재 있는 이미지 + 새로 추가할 이미지)
		if (currentCount >= 10) {
			alert('이미지는 최대 10개까지 업로드 가능합니다.');
			break; // 루프 종료 (계속하면 추가될 수도 있음)
		}

		// FileReader로 미리보기 추가
		const reader = new FileReader();
		reader.onload = (e) => {
			const preview = createElement(e, file);
			imagePreview.appendChild(preview);
		};

		reader.readAsDataURL(file);
		dataTransfer.items.add(file); // 실제 파일 저장
		currentCount++; // 새로 추가된 이미지 개수 증가
		prouductImgCnt++;
		$(".imgCount").text("(" + prouductImgCnt + "/10" + ")");
	}
}


// 태그 만들어주는 함수
function createElement(e, file) {
	const li = document.createElement('li');    // li 태그 만들기
	const img = document.createElement('img');  // img 태그 만들기
	img.setAttribute('src', e.target.result); // 만든 img 태그에 경로 속성 값 넣어줌
	img.setAttribute('data-file', file.name); // 만들 ing 태그에 파일 이름 속성 값 넣어줌
	img.setAttribute('data-modified', file.lastModified);
	img.setAttribute('data-size', file.size);
	checkProductRegist.productImg = true; 
	
	
	img.addEventListener("click", e => {  // 해당 이미지 클릭시
		console.log(prouductImgCnt);
		prouductImgCnt--; // 이미지 삭제시 개수 감소
		$(e.target).parent().remove(); // li안의 img까지 삭제
		$(".imgCount").text("(" + prouductImgCnt + "/10" + ")");
		
		 for(var i=0; i<dataTransfer.files.length; i++){
             if(dataTransfer.files[i].name==e.target.dataset.file){
                    dataTransfer.items.remove(i)
                    break;
             }
          }
		
		
		if(dataTransfer.files.length == 0){  
			checkProductRegist.productImg = false; 
		}
		
	});

	li.appendChild(img); // 이미지가 있는 li 태그 완성하여 li 리턴

	return li;
}


const realUpload = document.querySelector('.real-upload');
const upload = document.querySelector('.upload');

upload.addEventListener('click', () => realUpload.click()); // 이미지등록 클록시 input file타입 호출
realUpload.addEventListener('change', getImageFiles); // file타입에서 값 변경시키면 getImageFiles() 함수 호출

  • 대표카테고리의 값을 선택시 -> 그 하위 서브카테고리들이 출력되는 작업
		<div class="cate">
			<h4 class="h4Size">카테고리 *</h4>
				<select class="mainCate" onchange="chageSubCate(this.value);">
					<!-- this.value -> 선택된 option의 밸류값을 매개변수로 넣음 -->
					<%
                       <!-- categorys는 getAttribute를 통해서 디비안의 값들을 갖고온다 -->
					if (!categorys.isEmpty()) {
						for (int i = 0; i < categorys.size(); i++) {
					%>
					<option value="<%=categorys.get(i).getCategoryId()%>"><%=categorys.get(i).getCategoryName()%></option>
					<%
					}
				}
					%>
                      
                </select> 
                <select class="middleCate" name="subCate">
				<!-- 이안의 내용은 서브카테고리의 내용들이 출력된다 -->
				</select>
// 카테고리 선택하는 작업

$(() => {
	$(".mainCate").trigger("change", $(".mainCate:selected").val());  // 페이지로딩되었을때, 자동으로 change 함수 실행
	//	대상값은 현재 그 select에 선택된 값
})

function chageSubCate(value) {
	console.log(value);
	$.ajax({
		url: "findSubCate",
		data: { "cateId": value },
		success: function(result) {

			const subCate = result.split(","); // 문자열로 넘어온 값들을 ,를 구분자로 배열을 만듬

			$(".middleCate option").remove();   // 메인카테고리 선택할때마다 옵션들 다 삭제(초기화)
			for (let i = 0; i < subCate.length; i++) { // 배열에 있는 서브카테고리들을 option value로 만들어줌
				var option = $("<option value=" + subCate[i] + ">" + subCate[i] + "</option>");
				$(".middleCate").append(option);
			}
		},
		error: function() {
			console.log("카테고리 선택 오류발생");
		}
	})
}

컨트롤러

// findSubCate 서블릿
String cateId = request.getParameter("cateId");
List<String> subCategorys = new ProductRegistService().selectSubCate(cateId); // 서브카테고리들이 리스트로 나옴
// 리스트안에 문자열 형식으로 받아야함 

String result = subCategorys.stream().map(n->String.valueOf(n)).collect(Collectors.joining(","));
// 리스트를 -> 문자열로 만들어줌 (문자열로 만들어줄때 앞뒤로 공백을 없애고 ,로 구분해줌)
// [컴퓨터, 노트북, 스마트폰, 소프트웨어, 기타 주변기기]  -> 컴퓨터,노트북,스마트폰,소프트웨어,기타 주변기기

Mapper

	<select id="selectSubCate" resultType="string" parameterType="string">
		 SELECT SUBCATEGORY_NAME FROM SUBCATEGORY WHERE CATEGORY_ID = #{cateId}
	</select>   <!--반환타입 : 문자열,  매개변수 타입 : 문자열 -->
    <!-- 타입을 정수형으로 할 경우 -> _int -->
  • 가격입력시 숫자들만 입력가능하며, 중간중간에 , 로 숫자를 구별해줌
<input type="text" id="priceId" oninput="inputNumberFormat(this);" 
 placeholder="숫자만 입력해주세요." name="price">
<!-- oninput 속성을 통해 정규표현식을 적용하였다 -->
// ==== 가격 입력했을 때, 숫자만입력되고, 3자리수마다 ,로 구분해주는 작업
function comma(str) {
	str = String(str);
	return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,");
}

function uncomma(str) {
	str = String(str);
	return str.replace(/[^\d]+/g, "");
}

function inputNumberFormat(obj) {
	obj.value = comma(uncomma(obj.value));
}


// 가격이 입력될때마다 예외처리
const priceValue = document.getElementById("priceId")
const spanPrice = $("#spanPrice");
priceValue.addEventListener("keyup", function() {

	if (priceValue.value.length == 0) {
		spanPrice.text("");
		checkProductRegist.productPrice=false;
	}else{
		replacePrice = priceValue.value.replace(",","");
		if(replacePrice <= 0){
			spanPrice.text("0원보다 크게 입력하세요").css("color","red");
			checkProductRegist.productPrice=false;
		}else{
			spanPrice.text("○").css("color","green");
			checkProductRegist.productPrice=true;
		}
	}
});
  • 단어입력시, 해당 포함하는 연관 단어들을 선택 또는 엔터를 통해서 태그를 등록 가능
		<div class="relativeTag">
			<h4 class="h4Size">
				상품태그
			</h4>
			<input type="text" id="searchTag" placeholder="연관 태그를 입력해주세요" autocomplete="on">
			<div id="relativeTagDiv">
				<!-- 태그들이 등록되는 곳 -->
			</div>
		</div>

		<div class="autocomplete"><!-- 자동완성 검색어들이 나오는 곳 --></div>
// 상품태그 검색 관련 js
const dataList = [
  "#패션", "#패션의류", "#자켓", "#상의", "#스포츠", "#도서", "#전자기기", "#노트북", "#가구", "#생활", "#차량", "#악세서리",
  "#캠핑", "#등산", "#모니터", "#마우스", "#키보드", "#에어컨", "#헤드셋", "#레고", "#피규어", "#슬리퍼", "#책", "#소설", "#가방"
];

let registTagList = [];

const $searchTag = document.querySelector("#searchTag");
const $autoComplete = document.querySelector(".autocomplete");
const $relativeTagDiv = document.querySelector("#relativeTagDiv");

let nowIndex = 0;
let matchDataList = [];

$searchTag.addEventListener("keyup", (event) => {
  const value = $searchTag.value.trim();

  switch (event.keyCode) {
    case 38: // ↑
      nowIndex = Math.max(nowIndex - 1, 0);
      break;

    case 40: // ↓
      nowIndex = Math.min(nowIndex + 1, matchDataList.length - 1);
      break;

    case 13: // Enter
      if (matchDataList.length === 0) return;

      const selectedTag = matchDataList[nowIndex] || value;
      addTag(selectedTag);
      resetAutoComplete();
      return;

    case 27: // ESC
      resetAutoComplete();
      return;

    default:
      matchDataList = value
        ? dataList.filter((tag) => tag.toLowerCase().includes(value.toLowerCase()))
        : [];
      nowIndex = 0;
      break;
  }

  showList(matchDataList, value, nowIndex);
});

// 검색창 외부 클릭 시 자동완성 닫기
document.addEventListener("click", (event) => {
  const isClickInside = $searchTag.contains(event.target) || $autoComplete.contains(event.target);
  if (!isClickInside) {
    resetAutoComplete();
  }
});



$autoComplete.addEventListener("click", (e) => {
  let selected = e.target.textContent.trim();
  addTag(selected);
  resetAutoComplete();
});

function showList(data, keyword, highlightIndex) {
  const regex = new RegExp(`(${keyword})`, "gi");

  $autoComplete.innerHTML = data
    .map((tag, index) => {
      const highlighted = tag.replace(regex, "<label>$1</label>");
      return `<div class="${highlightIndex === index ? "active" : ""}">${highlighted}</div>`;
    })
    .join("");

  // ✅ 현재 highlight된 항목이 보이도록 스크롤
  const activeItem = $autoComplete.querySelector(".active");
  if (activeItem) {
    activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
  }
}

function addTag(tag) {
  if (!tag || registTagList.includes(tag)) {
	alert("이미 존재하는 키워드입니다.");
	return;
  }
  if (registTagList.length >= 5) {
    alert("태그는 최대 5개까지만 추가 가능합니다.");
    return;
  }

  registTagList.push(tag);

  const $li = document.createElement("li");

  const $label = document.createElement("label");
  $label.textContent = tag;

  const $button = document.createElement("button");
  $button.type = "button";
  $button.style.border = "none";
  $button.style.backgroundColor = "transparent";

  const $img = document.createElement("img");
  $img.src = (typeof contextPath !== "undefined" ? contextPath : "") + "/images/productregist/xbtn.png";
  $img.width = 15;
  $img.height = 15;

  $img.addEventListener("click", (e) => {
    const tagText = e.target.closest("li").querySelector("label").textContent;
    registTagList = registTagList.filter((t) => t !== tagText);
    $li.remove();
  });

  $button.appendChild($img);

  const $hiddenInput = document.createElement("input");
  $hiddenInput.type = "hidden";
  $hiddenInput.name = "tags";
  $hiddenInput.value = tag;

  $li.appendChild($label);
  $li.appendChild($button);
  $li.appendChild($hiddenInput);

  $relativeTagDiv.appendChild($li);
}

function resetAutoComplete() {
  matchDataList.length = 0;
  nowIndex = 0;
  $searchTag.value = "";
  $autoComplete.innerHTML = "";
}
  • 상품등록 하는 서블릿
		HttpSession session = request.getSession();
		String path=getServletContext().getRealPath("/upload/productRegist");  // ->  /upload/productRegist 안에다 업로드되는 이미지 넣음
		System.out.println(path);
		MultipartRequest mr= new MultipartRequest(request, path, 1024*1024*60,"UTF-8",new DefaultFileRenamePolicy()); 
		// MultipartRequest 객체 사용하려면, 이 서블릿을 요청시킨 form태그에 enctype="multipart/form-data" 를 넣어야함
		
		String replacePrice = mr.getParameter("price"); 
		replacePrice = replacePrice.replace(",",""); // ,있는 돈 문자열을 ,를 ""로 대체함
		
		Member m = (Member) session.getAttribute("loginMember");// 세션에서 현재로그인한 정보 갖고옴
		String productId = mr.getParameter("productId");
		String userId = m.getUserId();
		String title = mr.getParameter("title");
		String subCate = mr.getParameter("subCate");
		String place = mr.getParameter("place");
		String state = mr.getParameter("state");
		int price = Integer.parseInt(replacePrice); // 정수일경우 앞에 Integer.parseInt 로 형변환해야함
		String explan = mr.getParameter("explan");
		String tag = mr.getParameter("tag");
		
		List<ProductFile> files= new ArrayList();
		
		Enumeration<String> names=mr.getFileNames(); // 해당 파일객체들의 키값을 하나씩 출력 (Enumeration -> 열거객체 순환) 
		while(names.hasMoreElements()) {  // 다중 파일들의 이미지를 접근 가능
			String key=names.nextElement();  // key-> 해당 파일은 객체로 저장되잇음 각각 파일의 키를 저장함
//			mr.getFilesystemName(key));  new 파일
//			mr.getOriginalFileName(key)); ori파일

			files.add(ProductFile.builder().imageName(mr.getFilesystemName(key)).build()); 
			// ProductFile 객체 안에 멤버변수들을 builder를 통해서 각각 넣어줌 (파일이름, 등등)
			// builder 함수 사용시 마지막에 .build() 해줘야함
		}
		
		 Product p = Product.builder().title(title).productStatus(state).price(price).explanation(explan)
				 .keyword(tag).areaName(place).subCategoryName(subCate).files(files).build();
		 // Product 객체 안에 멤버변수들을 builder를 통해서 넣어줌 (마지막에는 files 멤버변수도 builder를 통해서 해당맞는 타입의 값을 넣어줌)
		 // Product 클래스안에 결국 ProudctFile 값들도 들어있는것임 
		 // 그러기 때문에 Product만 객체만 넣어줘도 됨
		 
		 int result = new ProductRegistService().insertProduct(p,userId); // 상품등록 및 상품이미지첨부파일 데이터 추가 하는 작업
		 
		 response.getWriter().print(result); // 해당 반환되는 0 또는 1의 값을 다시 js로 반환됨 (ajax이기 때문에 해줘야함)-> js에서 정수 값을 통해 분기처리

특히

profile
우측 상단 햇님모양 클릭하셔서 무조건 야간모드로 봐주세요!!

0개의 댓글