Hello It's good to be back ^_^

[POTG] - 해설1 본문

Project/[POTG] 오픈SW플랫폼

[POTG] - 해설1

HongYeon 2024. 12. 3. 18:41

2024-2학기 이화여자대학교 컴퓨터공학과 오픈SW플랫폼 수업을 수강하며 작업한 프로젝트 POTG에 대해 소개하고자 한다.

  • Project Of The Genius의 준말로, 게임에서 최고의 플레이를 뜻하는 Play Of The Game에서 유래했다.
  • POTG는 상품 등록 및 구입 시스템을 이용하여 학교 내 공동구매를 활성화하기 위해 제작된 프로젝트이다.
  • 마켓컬리를 레퍼런스로하여 제작하였으며, 수업이 요구하는 기능외에 공동구매, 마이페이지 기능을 구현하였다.

전체적인 화면 구조는 다음과 같다.

 

 

개발 기술 스택은 다음과 같다.

  • Frontend - html, css, javascript
  • Backend - Flask
  • Database - Firebase
  • 지도 API - 다음 우편번호 API
  • Git - Github Desktop을 이용해 버전 관리

 

1. 홈 화면

 


상단의 navigation 메뉴를 클릭해 각 해당 페이지로 이동할 수 있다

 

 

각각 사용자의 주소, 사용자가 누른 좋아요, 사용자의 장바구니 목록을 확인할 수 있다

 

 

등록된 상품 중 랜덤으로 4개의 상품을 미리 보여준다. 로그인 되어있다면 바로 장바구니에 해당 상품을 담을 수 있다

 

 

2. 회원가입 화면

 

사용자의 정보를 입력받아 db에 등록한다. 아이디와 이메일 중복체크가 가능하다.

이용약관 동의는 checkbox를 이용한 것인데 구현방법에 대해 소개하겠다.

 

2-1. opcaity 설정

 

 

우선 기본 checkbox 이미지를 다른 이미지로 덮어씌우기 위해선 opcaity를 0으로 설정해둔다. 

이렇게 하면 checkbox의 기능은 그대로 작동하되 눈에만 보이지 않게 된다.

 

 

opacity를 1로 설정할 시 기존의 체크박스가 화면에 보인다

 

 

해당 input 요소의 범위를 잘 설정한다.

삽입될 이미지가 있는 부분만 input의 범위로 지정하거나 나처럼 글씨까지 전부 포함해서 범위를 지정해도 된다.

 

 

2-2. 교체할 이미지를 넣는다

 

css로 적당히 위치 조절하여 원하는 곳에 체크박스 이미지를 넣는다

 

2-3. js 작성

 

해당 이미지 체크박스를 클릭할 때 마다 체크표시가 있는 이미지로 변경되어야 한다.

또한 전체 동의 버튼을 클릭시 모든 버튼의 이미지가 변경되어야 하고, 전체 동의 버튼을 클릭하지 않더라도 나머지 버튼들을 전부 클릭하면 전체 동의 버튼의 이미지도 변경되어야 한다.

 

 

html 코드

<div class = "termsCheckBox">
                        <label class = "labelFroAgreeAll" for = "TermsAgreeAll">
                            <input id = "TermsAgreeAll" type = "checkbox" class = "inputForAgree">
                            <div class = "divForCheckBox">
                                <img id = "TermsAgreeAllImg" src = "../static/images/체크박스1.png" style="width: 25px;">
                            </div>
                            <span style="margin-bottom: 5px; color: rgb(51, 51, 51);">전체 동의합니다</span>
                        </label>
                    </div>
                    <div class = "termsCheckBox">
                        <div class = "termsInnerCheckBox">
                            <label class = "labelForTermsAgree" for = "RequiredTermsCondition">
                                <input id = "RequiredTermsCondition" type = "checkbox" class = "inputForAgree" required>
                                <div class = "divForCheckBox">
                                    <img id = "RequiredTermsConditionImg" src = "../static/images/체크박스1.png" style="width: 25px;">
                                </div>
                                <span style="margin-bottom: 5px;color: rgb(51, 51, 51);">이용약관동의</span>
                            </label>
                            <span class = "termTextForRequired">(필수)</span>
                        </div>
                    </div>
                    <div class = "termsCheckBox">
                        <div class = "termsInnerCheckBox">
                            <label class = "labelForTermsAgree" for = "RequiredTermsOfPrivacy">
                                <input id = "RequiredTermsOfPrivacy" type = "checkbox" class = "inputForAgree" required>
                                <div class = "divForCheckBox">
                                    <img id = "RequiredTermsOfPrivacyImg" src = "../static/images/체크박스1.png" style="width: 25px;">
                                </div>
                                <span style="margin-bottom: 8px;color: rgb(51, 51, 51);">개인정보 수집∙이용 동의</span>
                            </label>
                            <span class = "termTextForRequired">(필수)</span>
                        </div>
                    </div>

 

 

js 코드

// 체크박스 이미지 변경 함수
function updateCheckboxImage(checkboxId, imageId) {
    const checkbox = document.getElementById(checkboxId);
    const image = document.getElementById(imageId);
    image.src = checkbox.checked ? "../static/images/체크박스2.png" : "../static/images/체크박스1.png";
}

// TermsAgreeAll 상태 업데이트 함수
function updateTermsAgreeAllState() {
    const conditionChecked = requiredBtn.checked;
    const privacyChecked = privacyBtn.checked;

    // 두 체크박스가 모두 체크되면 TermsAgreeAll 체크
    agreeAllBtn.checked = conditionChecked && privacyChecked;

    // TermsAgreeAll 상태에 맞는 이미지 업데이트
    updateCheckboxImage("TermsAgreeAll", "TermsAgreeAllImg");
}

// 각 체크박스 상태 변경 시 이미지 업데이트 및 TermsAgreeAll 상태 체크
function handleCheckboxChange() {
    // 이미지 업데이트
    updateCheckboxImage(this.id, this.id + "Img");

    // TermsAgreeAll 상태 업데이트
    updateTermsAgreeAllState();
}

// TermsAgreeAll 체크박스 클릭 시 모든 체크박스 상태 동기화
agreeAllBtn.addEventListener("change", function () {
    const isChecked = this.checked;
    requiredBtn.checked = isChecked;
    privacyBtn.checked = isChecked;

    // 이미지 업데이트
    updateCheckboxImage("TermsAgreeAll", "TermsAgreeAllImg");
    updateCheckboxImage("RequiredTermsCondition", "RequiredTermsConditionImg");
    updateCheckboxImage("RequiredTermsOfPrivacy", "RequiredTermsOfPrivacyImg");
});

// 개별 체크박스의 상태 변경 시 이미지 업데이트 및 TermsAgreeAll 상태 확인
requiredBtn.addEventListener("change", handleCheckboxChange);
privacyBtn.addEventListener("change", handleCheckboxChange);

 

 

회원 가입시 전화번호 부분은 -를 제외하고 숫자만 입력받아야 한다. 

그러나 type = "tel"로 지정하여도 여전히 문자열 입력이 가능하다. 숫자만 입력 가능하게 구현하는 방법을 소개하겠다.

 

 

해당 input 태그에 oninput 부분 속성 1줄만 추가해주면 된다.

oninput = "this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1');"

 

 

 

3. 로그인 화면

 

사용자에게 아이디와 비밀번호를 입력받아 db에 일치하는 내용이 있으면 로그인 된다.

 

 

아이디 찾기와 비밀번호 찾기 기능이 가능하다.

 

 

 

사용자가 회원가입시 사용한 이름과 전화번호를 입력시 db에서 정보에 맞는 id를 찾아와 팝업 메세지로 알려준다.

 

4. 상품 등록 화면

 

상품의 정보를 입력하고 상품을 등록할 수 있다.

여기서 주소 찾기 버튼을 클릭해 실제로 다음 우편번호 찾기 API가 호출된다.

 

 

주소를 찾은 뒤 클릭하면 실제 주소 입력창에 자동 입력된다.

 

구현 로직

<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>

 

우선 script에 해당 API 링크를 추가한다.

 

<div class="input-group address-input">
	<label for="address">주소</label>
		<div class="address-container">
 			<input type="text" name="address" id="address" placeholder="주소를 입력해주세요." required>
      			<button type="button" class="address-button" onclick="fetchAddress()">주소 찾기</button>
		</div>
</div>

 

주소 찾기 버튼을 클릭할 때 호출할 함수를 삽입한다.

여기선 fetchAddress() 함수로 지정하였다.

 

function fetchAddress() {
	new daum.Postcode({
    	oncomplete: function (data) {
                document.getElementById('address').value = data.roadAddress || data.jibunAddress;
            }
        }).open();
}

 

API를 호출하는 함수를 작성한다

 

 

5. 상품 조회 화면

 

등록된 상품을 조회할 수 있다

 

 

카테고리별로 상품을 조회할 수도 있다

 

6 상품 상세 조회 화면

 

상품 조회해서 상품을 클릭시, 해당 상품의 상세 조회 화면으로 넘어간다.

 

 

 

사용자는 해당 상품에 좋아요를 누르거나 장바구니 담기, 구매를 할 수 있다

 

7. 리뷰 작성 화면

 

6에서 장바구니에 상품을 담아 구매하거나 바로 구매했을 경우 거래 내역 페이지로 넘어간다.

해당 페이지에서는 구매한 상품들의 목록을 볼 수 있고 오른쪽의 리뷰 작성하기 버튼을 클릭해 리뷰를 작성할 수 있다

 

 

 

리뷰 작성하기 버튼 클릭시 실제 리뷰를 작성할 수 있는 페이지로 넘어간다.

해당 페이지에서는 자동으로 상품의 이름, 가격, 리뷰작성자의 이름, 리뷰작성자의 아이디가 나타난다.

여기서 상품명과 가격을 사용자에게 직접 입력받는게 아니기 때문에 db에 리뷰를 등록할 때 어려움이 생긴다.

 

 

따라서 해당 페이지에 input태그의 type 속성을 hidden으로 주고 상품명과 가격을 백엔드로 넘어가게 설정하였다.

 

 

8. 리뷰 조회 화면

 

db에 등록된 상품들을 보여준다.

리뷰 작성한 사용자의 아이디, 리뷰의 제목, 리뷰에 첨부된 사진, 상품의 사진, 상품명을 동적으로 가져와 표시한다.

 

 

 

리뷰위에 마우스를 올리면 리뷰 사진이 불투명해지면서 사용자의 클릭을 유도한다.

 

9. 리뷰 상세 조회 화면

 

리뷰 상세 페이지로 넘어가면 해당 리뷰의 작성자가 입력했던 리뷰의 내용과 별점까지 추가로 확인할 수 있다.

 

 

10. 마이페이지

10-1. 좋아요 누른 상품

 

 

사용자가 좋아요를 누른 상품을 따로 모아서 볼 수 있다.

상품에 마우스를 올리면 상품의 정보가 나타나고 클릭시 해당 상품의 상세페이지로 이동한다.

 

 

마우스를 올리면 해당 상품의 정보가 나타나게 하는 법

 

해당 상품의 정보는 동적라우팅을 이용해 가져온다.

그리고 상품의 정보를 묶는 div의 css 속성을 다음과 같이 지정한다.

opacity: 0;
transition: all 0.3s ease-in-out;
z-index: 10;

 

opacity : 0;

기존적으로 안보이게 설정한다. 마우스를 올렸을 때만 해당 정보가 나오게 해야 한다.

 

transition: all 0.3s ease-in-out;

마우스를 올리면 해당 요소의 opacity가 0에서 1로 변하는데, 이때 시간에 따라 바뀌도록 하는 것이다.

all의 경우 변하는 모든 요소에 transition을 주겠다는 의미이고, 0.3s가 transition이 걸리는 시간, ease-in-out은 다음 표 참고.

transition 효과의 시작, 중간, 끝의 속도를 지정할 수 있다

 

 

z-index: 10;

레이어 상으로 가장 위에 있도록 한다. 마우스를 올렸을 때, 상품의 이미지보다 레이어 상으로 더 위에 상품 정보가 나타나야 하니 z-index값을 지정해줘야 한다. 단 z-index를 이용하기 위해선 position 속성이 지정되어있어야 하고 static이 아니여야 한다.

 

css에 해당 클래스에 가상 클래스 hover를 추가해 작성한다.

.itemInfo{
    width: inherit;
    height: 177px;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    transition: all 0.3s ease-in-out;
    z-index: 10;
    color: #e7e2e2;
    position: relative;
    text-align: center;
    font-size: 12px;
    line-height: 20px;
    padding-top: 10px;
}

.itemInfo:hover{
    opacity: 1;
}

 

마우스를 올렸을 때는 opacity가 1로 바뀌도록 css를 작성한다.

 

10-2. 장바구니

 

 

마이페이지에서 왼쪽의 장바구니 버튼을 클릭하거나 오른쪽 위에서 클릭해 장바구니 페이지로 이동할 수 있다.

또는 상품 상세 조회 화면에서 상품을 장바구니에 담으면 장바구니 페이지로 이동한다.

 

 

장바구니에는 이론상 무한개의 상품을 담을 수 있다. 

장바구니 결제 금액은 상품의 가격을 누적합하여 표시한다.

상단의 X버튼을 눌러 장바구니에 있는 상품을 뺄 수도 있다.

 

 

장바구니에서도 상품의 이미지 위에 마우스를 올려 해당 상품의 상세 페이지로 이동할 수 있다.

구현 방식은 좋아요 누른 상품 모아보기에서 구현한 방식과 동일하다.

 

 

 

'Project > [POTG] 오픈SW플랫폼' 카테고리의 다른 글

[POTG] - 팁/디버깅  (0) 2024.12.03
[POTG] - 개념 소개  (1) 2024.12.03