어제 못한 유저 페이지 프론트 구현

main은 css 는 완료 했고 js도 버그를 제외한 기능 완료

info, cart, order, review의 html과 css 적용 완료

남은건 java script의 적용과 버그 수정과 더미 db 파일 적용

 

숫자 증감이나 별 클릭시 표시 

 

 

info.html - 숫자 증감과  체크 사용자 버튼 구현

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/info.css" rel="stylesheet" type="text/css">
    <script src="/js/user/info.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/info" method="post" class="info-form">
            <div class="menu-image">
                <img src="/images/sample.png" alt="음식 이미지" class="menu-img">
            </div>
            <div class="info">
                <div class="title">짜파게티</div>
                <div class="contents">맛있는 짜파게티 김치 미포함</div>
                <div class="price">300원</div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="text">매운맛</div>
                    <div class="select">필수</div>
                </div>
                <div class="contents">
                    <label class="select">
                        <input type="radio" name="spiciness" class="check" value="spicy">
                        <label></label>
                        <span class="content">맵게 해주세요</span>
                        <span class="price">+3000원</span>
                    </label>
                    <label class="select">
                        <input type="radio" name="spiciness" class="check" value="not-spicy">
                        <label></label>
                        <span class="content">안맵게 해주세요</span>
                        <span class="price">+4000원</span>
                    </label>
                </div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="text">토핑</div>
                    <div class="select">선택</div>
                </div>
                <div class="contents">
                    <label class="select">
                        <input type="checkbox" class="check">
                        <label></label>
                        <span class="content">치즈</span>
                        <span class="price">1000원</span>
                    </label>
                    <label class="select">
                        <input type="checkbox" class="check">
                        <label></label>
                        <span class="content">감자</span>
                        <span class="price">1000원</span>
                    </label>
                </div>
            </div>
            <div class="count">
                <div class="inner">
                    <div class="text">수량</div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">1</div>
                        <input type="button" class="plus" value="+">
                    </div>
                </div>
                <input type="button" class="submit button" value="300원 담기" onclick="infosubmit(form)">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

info.css

@charset "UTF-8";

.container form{
    width: 100%;
    margin: 0;
    padding: 0;
    margin-bottom: var(--fixedBtn-height);
}
/* 메인 사진 */
.container .menu-image{
    width: 100%;
    height: 250px;
    overflow: hidden;
}

.container .menu-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 음식 설명 */
.container .info{
    padding: var(--padding);
    margin: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

.container .info .title{
    font-size: var(--font-big);
    font-weight: bold;
    margin-bottom: 10px;
    color: var(--black);
}

.container .info .contents{
    font-size: var(--font-small);
    color: var(--light-gray);
}

.container .info .price{
    font-size: var(--font-middle);
    color: var(--black);
    text-align: right;
}
/* 옵션 사용자 버튼 */
.check{
    display: none;
}
/* 라벨의 가상 요소를 이용해 체크박스 스타일링 */
.select .check[type="checkbox"] + label::before {
    content: '\f0c8'; /* Font Awesome의 square 아이콘 */
    font-family: 'Font Awesome 5 Free';
    font-weight: 500;
    color: var(--light-gray);
    font-size: var(--font-middle);
    margin-right: var(--padding);
}

/* 체크된 체크박스의 스타일 */
.select .check[type="checkbox"]:checked + label::before {
    content: '\f14a'; /* Font Awesome의 check-square 아이콘 */
    color: var(--orange);
}

/* 라벨의 가상 요소를 이용해 라디오 버튼 스타일링 */
.select .check[type="radio"] + label::before {
    content: '\f111'; /* Font Awesome의 circle 아이콘 */
    font-family: 'Font Awesome 5 Free';
    font-weight: 500;
    color: var(--light-gray);
    font-size: var(--font-middle);
    margin-right: var(--padding);
}

/* 체크된 라디오 버튼의 스타일 */
.select .check[type="radio"]:checked + label::before {
    content: '\f058'; /* Font Awesome의 check-circle 아이콘 */
    color: var(--orange);
}

/* 옵션 */
.container .option{
    margin: 0;
    display: flex;
    flex-direction: column;
}

/* 옵션 이름 부분 */
.container .option .title{
    padding: var(--padding);
    width: 100%;
    display: flex;
    justify-content: space-between;
    background-color: var(--extra-light-gray);
    color: var(--light-gray);
}

.container .option .title .text{
    padding: 10px;
    color: var(--gray);
}

.container .option .title .select{
    padding: 10px;
    background-color: var(--light-gray);
    color: var(--gray);
}

/* 옵션 내용 선택 부분 */
.container .option .contents{
    padding: var(--padding);
    width: 100%;
    display: flex;
    flex-direction: column;
}

.container .option .contents .select{
    display: flex;
    padding: 10px 0;
}

.container .option .contents .select .content{
    flex: 1;
}

/* 수량 */
.container .count {
    position: fixed;
    left: 50%;
    bottom: 0;
    margin: 0;
    /* 자신의 너비의 반만큼 왼쪽으로 이동하여 정확히 중앙에 위치하도록 조정 */
    transform: translateX(-50%);
    width: var(--innerBox-width);
    background-color: white;
    z-index: 1; /* 필요에 따라 조정 */
    padding: var(--padding);
}
.container .count .inner{
    display: flex;
    justify-content: space-between; /* 내부 요소를 양 끝으로 정렬 */
    width: 100%; /* 부모 컨테이너의 전체 너비 사용 */
    padding: 0 20px; /* 좌우 패딩 추가로 내부 요소들 사이 간격 조정 */
    margin-bottom: 10px;
}
.container .count .inner .text {
    font-size: var(--font-content); /* 필요에 따라 조정 */
    color: var(--black);
    display: flex;
    justify-content: center;
    align-items: center;
}

.container .count .inner .num {
    padding: 5px;
    display: flex;
    justify-content: right;
    border: 2px solid var(--light-gray);
}

.container .count .inner .num .minus,
.container .count .inner .num .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container .count .inner .num .text{
    width: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 버튼 */
.container .count .submit{
    width: calc(100% - 20px); /* 패딩 고려하여 너비 조정 */
}

 

info.js

/* form 하기 전에 체크 */

function infosubmit(form) {
    form.submit();
}

// 수량 증감 액션
const minus = document.querySelector('.minus');
const plus = document.querySelector('.plus');
const text = document.querySelector('.count .inner .num .text');

// 수량 증가 함수
function plusNum() {
    let num = parseInt(text.innerHTML);
    if (num < 100) {
        num += 1;
        text.innerHTML = num;
    }
}

// 수량 감소 함수
function minusNum() {
    let num = parseInt(text.innerHTML);
    if (num > 1) {
        num -= 1;
        text.innerHTML = num;
    }
}

// 마이너스 버튼 눌렀을 때
minus.addEventListener('mousedown', e => {
    minusNum(); // 수량 감소
});

// 플러스 버튼 눌렀을 때
plus.addEventListener('mousedown', e => {
    plusNum(); // 수량 증가
});

 

cart.html - 숫자 증감을 개별적으로 구현 메뉴추가 메인 링

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>장바구니</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/cart.css" rel="stylesheet" type="text/css">
    <script src="/js/user/cart.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/order" method="post">
            <div class="restaurant-name">더조은 식당</div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">짜파게티</div>
                        <div class="menu-price">가격 : 5,000원</div>
                        <div class="option">옵션 : 안맵게 해주세요(-3000원)</div>
                        <div class="total-price">3000원</div>
                    </div>
                </div>
                <div class="etc">
                    <div class="image">
                        <img src="/images/sample.png" alt="음식 이미지" class="img">
                    </div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">2</div>
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
            </div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">짜파게티2</div>
                        <div class="menu-price">가격 : 4,000원</div>
                        <div class="option">옵션 : 맵게 해주세요(3000원)</div>
                        <div class="total-price">10000원</div>
                    </div>
                </div>
                <div class="etc">
                    <div class="image">
                        <img src="/images/sample.png" alt="음식 이미지" class="img">
                    </div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">3</div>
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
            </div>
            <div class="menu-plus">
                +메뉴추가
            </div>
            <div class="request">
                <label for="contents" class="title">요청사항</label>
                <textarea class="contents" id="contents" name="contents" placeholder="내용을 입력하세요" rows="4"></textarea>
                <input type="button" class="submit button" onclick="cartsubmit(form)" value="3000원 주문하기">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

cart.css

@charset "UTF-8";

.container form{
    margin: 0;
    width: 100%;
    padding: 0;
}

form>div{
    margin: 0;
    padding: 20px;
}

/* 식당 이름 */
.container form .restaurant-name{
    font-size: var(--font-middle);
    font-weight: bold;
    border-top: 1px solid var(--light-gray);
}

/* 주문 리스트 */
.container form .orders{
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    border-top: 1px solid var(--light-gray);
}
.container form .orders .menu{}

.container form .orders .menu .text{
}
.container form .orders .menu .text .title,
.container form .orders .menu .text .total-price{
    font-size: var(--font-content);
    color: var(--black);
    padding-bottom: 10px;
}

.container form .orders .menu .text .menu-price,
.container form .orders .menu .text .option
{
    font-size: var(--font-small);
    color: var(--gray);
    padding-bottom: 10px;
}

.container form .orders .menu .text .total-price{
    padding-top: 10px;
}

.container form .orders .etc{
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
.container form .orders .etc .image{
    margin-left: auto; /* 오른쪽 정렬 */
    width: 90px;
    height: 90px;
    overflow: hidden;
}

.container form .orders .etc .image img{
    width: 100%;
    height: auto;
}

.container form .orders .etc .num{
    padding: 5px;
    display: flex;
    border: 1px solid var(--light-gray);
}
.container form .orders .etc .num .minus,
.container form .orders .etc .num .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container form .orders .etc .num .text{
    width: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 새로운 매뉴 추가 */
.container form .menu-plus{
    background-color: var(--extra-light-gray);
    text-align: center;
    color: var(--gray);
}

/* 요청사항 */
.container form .request{
    
    padding-top: 40px;
}
.container form .request .title{
    font-weight: bold;
}
.container form .request .contents{
    border: 1px solid var(--light-gray);
}
.container form .request .submit{}

 

cart.js

// form 관련
function cartsubmit(form){
    form.submit();
}

// 숫자 증감

const minus = document.querySelectorAll('.minus');
const plus = document.querySelectorAll('.plus');

// 수량 증가 함수
function plusNum(text) {
    let num = parseInt(text.innerHTML);
    if (num < 100) {
        num += 1;
        text.innerHTML = num;
    }
}

// 수량 감소 함수
function minusNum(text) {
    let num = parseInt(text.innerHTML);
    if (num > 1) {
        num -= 1;
        text.innerHTML = num;
    }
}

minus.forEach( e =>{
    e.addEventListener('mousedown', () => {
        // 'text' 요소를 이 버튼과 가장 가까운 상위 요소에서 찾습니다.
        const text = e.closest('.num').querySelector('.text');
        minusNum(text); // 수량 감소
    });
});

plus.forEach( e =>{
    e.addEventListener('mousedown', () => {
        // 'text' 요소를 이 버튼과 가장 가까운 상위 요소에서 찾습니다.
        const text = e.closest('.num').querySelector('.text');
        plusNum(text); // 수량 증가
    });
});


// 메뉴 추가 버튼 클릭 시 메인으로
const menuPlus = document.querySelector(".menu-plus");

menuPlus.addEventListener('click', () =>{
    location.href = '/user/main';
});

 

order.html - 여긴 그냥 스타일 다듬기

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="/css/style.css" type="text/css">
    <link rel="stylesheet" href="/css/manager/order.css" type="text/css">
    <script src="/js/manager/order.js" defer type="text/javascript"></script>
</head>
<body>
    <div class="innerBox">
        <header>
            <div>
                <a href=""><i class="fas fa-home"></i></a>
            </div>
            <p>주문관리</p>
            <div>
                <a href="#"></a>
            </div>
        </header>
        <div class="container">
            <ul class="order-state-summary">
                <li class="call">
                    <p class="state-name">호출</p>
                    <p class="state-content"><span>9</span>건</p>
                </li>
                <li class="wait">
                    <p class="state-name">주문</p>
                    <p class="state-content"><span>3</span>건</p>
                </li>
                <li class="okay">
                    <p class="state-name">승인</p>
                    <p class="state-content"><span>1</span>건</p>
                </li>
            </ul>
            <button onclick="location.href='/manager/sales'">총 매출 확인하기 ></button>
            <ul class="list-select">
                <li class="on">전체</li>
                <li>호출</li>
                <li>대기</li>
                <li>승인</li>
            </ul>
            <ul class="order-list">
                <li>
                    <div class="content-summary">
                        <div class="left">
                            <p class="order-title call">호출</p>
                            <p class="order-subtitle">와주세요</p>
                            <span class="table-name">1번 테이블</span>
                        </div>
                        <div class="right">
                            <span class="time">13:00</span>
                        </div>
                    </div>
                </li>
                <li>
                    <div class="content-summary">
                        <div class="left">
                            <p class="order-title wait">주문</p>
                            <span class="table-name">1번 테이블</span>
                        </div>
                        <div class="right">
                            <span class="time">13:00</span>
                            <button><i class="fas fa-chevron-down"></i></button>
                        </div>
                    </div>
                    <table class="info">
                        <tr>
                            <th>상품이름</th>
                            <th>수량</th>
                            <th>금액</th>
                        </tr>
                        <tr>
                            <td>짜파게티</td>
                            <td>2</td>
                            <td>3,000</td>
                        </tr>
                        <tr>
                            <td>토마토 파스타</td>
                            <td>23</td>
                            <td>9,000</td>
                        </tr>
                        <tr>
                            <td>총 합계</td>
                            <td>25</td>
                            <td>267,000</td>
                        </tr>
                    </table>
                </li>
            </ul>
        </div>
    </div>
</body>
</html>

 

order.css

@charset "UTF-8";

.container>div{
    margin: 0;
    padding: 20px;
    border-bottom: 1px solid var(--light-gray);
}

/* 식당 이름 */
.container .restaurant-name{
    width: 100%;
    font-size: var(--font-middle);
    font-weight: bold;
    text-align: left;
}

/* 주문 목록 */
.container .order{
    width: 100%;
    display: flex;
    justify-content: space-between;
}
.container .order .content{}

.container .order .content .title{
    font-size: var(--font-content);
    color: var(--black);
    padding-bottom: 10px;
}
.container .order .content .before-price,
.container .order .content .option{
    font-size: var(--font-small);
    color: var(--gray);
    padding-bottom: 10px;
}

.container .order .content .option{
    padding-bottom: 0;
}
.container .order .after-price{
    display: flex;
    align-items: end;
}

/* 총 금액 */
.container .total-price{
    border-bottom: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
}
.container .total-price .title{}
.container .total-price .content{}

/* 요청사항 */
.container .request{
    border-bottom: 0;
    width: 100%;
    background-color: var(--extra-light-gray);
}
.container .request .title{
    padding-bottom: 20px;
}
.container .request .content{
    font-size: var(--font-small);
    color: var(--gray);
}
/* 기타? */
.container .etc{
    border-bottom: 0;
    width: 100%;
    padding: 20px;
}
/* 경고 문구 */
.container .etc .warning{
    font-size: var(--font-small);
    color: var(--red);
    padding-bottom: 20px;
    text-align: center;
}

/* 버튼 */
.container .etc .submit{
    width: 100%;
}

 

order.js

없음 나중에 백엔드 구현할때 할듯

 

review.html - 여긴 별 클릭 했을시 값변경과 별 색 변경

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>리뷰작성</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/review.css" rel="stylesheet" type="text/css">
    <script src="/js/user/review.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/review" method="post">
            <div class="restaurant-name">더조은 식당</div>
            <div class="stars">
                <div class="star-span">
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <input type="hidden" name="star" id="star" class="star">
                </div>
            </div>
            <div class="nickname">
                <label class="title">닉네임</label>
                <input type="text" class="contents" placeholder="닉네임을 입력해 주세요"></input>
            </div>
            <div class="review">
                <label class="title">리뷰 내용</label>
                <textarea class="contents" cols="3" rows="5"></textarea>
            </div>
            <div class="buttons">
                <input type="button" class="submit button" onclick="reviewCheck(form)" value="리뷰 작성">
            </div>
        </form>
    </div>
    <footer></footer>
</div>

</body>
</html>

 

review.css

@charset "UTF-8";

.container form{
    margin: 0;
    padding: 0;
}

form{
    width: 100%;
}

form>div{
    margin: 0;
    padding: 20px;
}

/* 식당 이름 */
.container form .restaurant-name{
    width: 100%;
    font-size: var(--font-middle);
    font-weight: bold;
    text-align: left;
}

/* 별 */
.container form .stars{
    width: 100%;
    padding: 0 20px;
}

.container form .stars .star-span{
    font-size: var(--font-big);
    color: var(--light-gray);
    cursor: default;
}

.container form .stars .star-span span{
    margin: -2px;
    cursor: pointer;
}

/* 닉네임 */
.container form .nickname{
    width: 100%;
}

.container form .nickname .title{
    margin-bottom: 10px;
}

.container form .nickname .contents{
    border: 1px solid var(--light-gray);
}

/* 리뷰 내용 */
.container form .review{
    width: 100%;
}

.container form .review .title{
    margin-bottom: 10px;
}

.container form .review .contents{
    margin: 0;
    border: 1px solid var(--light-gray);
}

/* 버튼 */
.container form .buttons{
    padding-top: 0;
}
.container form .buttons .submit{
    font-size: var(--font-middle);
}

 

review.js

function reviewCheck(form){
    form.submit();
}

// 별 클릭시 색 변하는 이벤트
const stars = document.querySelectorAll('.star-span span');
const text = document.querySelector('.star-span .star');

stars.forEach( (e, idx) =>{
    e.addEventListener('click', () =>{
        updateStars(idx);
    })
});

// 별을 i 번째 만큼 색 바꿈
function updateStars(idx){
    stars.forEach( (e, i) =>{
        text.value = idx+1;
        if(i <= idx){
            e.style.color = '#FFBE3F';
        }else{
            e.style.color = '#c2c2c2';
        }
    });
}

 

main구현할때 gpt 한테 다 해달래 했더니 너무 복잡해짐 
나머진 최대한 혼자서 해서 수정이 쉬웠는데
편한데 수정이 어렵다는 단점이 있음

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 9일차  (0) 2024.04.08
[Spring/AWS] 팀프로젝트 8일차  (0) 2024.04.06
[Spring/AWS] 팀프로젝트 6일차  (0) 2024.04.03
[Spring/AWS] 팀프로젝트 5일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02

오늘은 어제에 이어 페이지 연결과 html css javascript 위주로 진행

 

user가 볼수 있는 main 페이지와 info, cart, order, review의 html 태그와 css 정도는 마무리 하고

main 자바 스크립트나 위치 스타일 마무리
info 페이지도 cs와 js 마무리

main, info는 thymeleaf 받을 태그 정도는 만들어 두고 나중에 controller 쪽 만지면 완료

 

 

오늘 배운것 css에서 gap과 sticky, script에서 DOMContentLoaded, getBoundingClientRect()

 

gap: 10px; 이건 각 항목의 간격을 고르게 주더라고 가운데는 벌어지는데 그걸 일정하게 해줌

 

position: sticky; 이건 해당 영역을 벗어나면 따라오게 해주는 신기한 명령

 

DOMContentLoaded, 페이지에서 무든 이벤트가 있을 때마다 감지 이걸 많이 쓰면 렉걸리려나

 

getBoundingClientRect, 스크롤 감지 관련 문제 때문에 GPT에게 물어가면서 함 GPT 만세

 

오늘 수정한 코드

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
    <script src="/js/user/main.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <div class="restaurant-image">
            <img src="/images/sample.png" alt="식당 메인 이미지" class="restaurant-img">
        </div>
        <div class="title">
            <div class="restaurant-name">더조은 식당</div>
            <button class="report-button button">
                <i class="fas fa-exclamation-triangle"></i>신고하기
            </button>
        </div>
        <div class="ratings-reviews">
            <div class="ratings">
                <div class="stars">★★★★★</div>  <!-- 5개의 별을 기본으로 설정 -->
                <label>3.3</label> <!-- 예시 평점 -->
            </div>
            <button class="call-button button">호출하기</button>
        </div>
        <div class="buttons">
            <button class="menu-btn button">메뉴</button>
            <button class="announcement-btn button">공지사항</button>
            <button class="reviews-btn button">리뷰</button>
        </div>
        <div class="content">
            <div class="menu-content">
                <div class="category-content">
                    <div th:each="category : ${categories}" class="category" th:text="${category.name}" th:data-target="'menu-category-' + ${category.id}"></div>
                </div>
                <div class="menus-container">
                    <div th:each="category : ${categories}" class="menu-category"  th:id="'menu-category-' + ${category.id}" th:data-category-id="${category.id}" >
                        <div th:text="${category.name}" class="category-name"></div>
                        <div class="menus" th:each="menu : ${menus}">
                            <div th:if="${menu.category.id} == ${category.id}" class="menu" th:onclick="'location.href=\'/user/info?id=' + ${menu.id} + '\''">
                                <div class="image">
                                    <img src="/images/sample.png" alt="메뉴 이미지" class="restaurant-image">
                                </div>
                                <div class="text">
                                    <div th:text="${menu.name}" class="menu-name"></div>
                                    <!--th:text="${menu.info}"-->
                                    <div class="menu-info">맛있음</div>
                                    <div th:text="${menu.price + '원'}" class="menu-price"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="announcement-content">
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항1</div>
                        <div class="date">16:12</div>
                    </div>
                    <div class="content">쉬고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항2</div>
                        <div class="date">2024.04.03</div>
                    </div>
                    <div class="content">아파서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항3</div>
                        <div class="date">2024.04.02</div>
                    </div>
                    <div class="content">싱숭생숭해서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항4</div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">해어져서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항5</div>
                        <div class="date">2024.03.20</div>
                    </div>
                    <div class="content">그냥 쉽니다</div>
                </div>
            </div>
            <div class="review-content" >
                <div class="review">
                    <div class="title">박윤재</div>
                    <div class="mid">
                        <!-- 여기에 별점을 표시할 div를 추가합니다. 데이터 속성(data-rating)을 사용하여 별점을 지정합니다. -->
                        <div class="star" data-rating="5"></div>
                        <div class="date">1달전</div>
                    </div>
                    <div class="content">음식이 친절하고 사장님이 맛있어요</div>
                </div>
                <div class="review">
                    <div class="title">손지영</div>
                    <div class="mid">
                        <div class="star" data-rating="4"></div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">1주전</div>
                </div>
                <div class="review">
                    <div class="title">윤경재</div>
                    <div class="mid">
                        <div class="star" data-rating="1"></div>
                        <div class="date">2시간전</div>
                    </div>
                    <div class="content">집에 보내주세요</div>
                </div>
            </div>
        </div>
    </div>
    <footer></footer>
    <!-- 신고하기 모달 창 -->
    <div id="reportModal" class="modal report-modal">
        <div class="modal-content">
            <h2 class="modal-title">가게 신고</h2>
            <p class="modal-warning">가게 신고 시 돌이킬 수 없으며,<br>허위로 신고를 작성한 경우<br>신고는 무효화 됩니다.</p>
            <label for="reportReason" class="modal-label">신고 사유</label>
            <textarea id="reportReason" class="modal-input"></textarea>
            <button class="modal-submit button" id="reportCall">신고하기</button>
        </div>
    </div>
    <!-- 호출하기 모달 창 -->
    <div id="callModal" class="modal call-modal">
        <div class="modal-content">
            <h2 class="modal-title">호출하기</h2>
            <form id="callForm">
                <label><input type="radio" name="callOption" value="water" checked/> 물 주세요</label><br />
                <label><input type="radio" name="callOption" value="cup" /> 컵 주세요</label><br />
                <label><input type="radio" name="callOption" value="towel" /> 물수건 주세요</label><br />
                <label><input type="radio" name="callOption" value="staff" /> 그냥 직원오세요</label>
            </form>
            <button class="modal-submit button" id="submitCall">호출하기</button>
        </div>
    </div>
</div>
</body>
</html>

 

main.css

@charset "UTF-8";

/* 메인 이미지 */
.container .restaurant-image{
    width: 100%;
    height: 200px;
    overflow: hidden;
}

.container .restaurant-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 타이틀과 신고버튼 */
.container .title{
    width: 100%;
    padding: var(--padding);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 타이틀 */
.container .title .restaurant-name{
    font-size: var(--font-big);
    font-weight: bold;
}

/* 신고 */
.container .title .report-button{
    padding: 0;
    background-color: transparent;
    color: red;
    border: none;
    font-size: var(--font-small);
}

.container .title .report-button .fa-exclamation-triangle{
    margin-right: 5px;
}

/* 별표와 리뷰 버튼*/
.container .ratings-reviews{
    margin-bottom: 10px;
    padding: var(--padding);
    padding-top: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 별표 부분*/
.container .ratings-reviews .ratings {
    display: flex;
    align-items: center;
}

/* 별표의 별 부분 */
.container .ratings-reviews .ratings .stars{
    position: relative;
    display: inline-block;
    font-size: 20px; /* 별 크기 조정 */
    direction: rtl; /* 별을 오른쪽에서 왼쪽으로 채워나가기 위해 */
}

.container .ratings-reviews .ratings .stars::after{
    content: "★★★★★";
    color: lightgray; /* 기본 별 색상 설정 */
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 0;
 }

.star-fill {
    display: block;
    color: gold; /* 금색 별 색상 설정 */
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    z-index: 1;
    direction: ltr; /* 금색 별이 왼쪽에서 시작하도록 설정 */
}

/* 별표의 숫자 부분 */
.container .ratings-reviews .ratings label{
    margin-left: 10px;
}

/* 호출 버튼 부분*/
.container .ratings-reviews .call-button{
    border: 0;
    border-radius: 0;
    padding: 10px 30px;
}


/* 메뉴 공지사항 리뷰 */
.container .buttons{
    border-bottom: 3px solid var(--orange);
    display: flex;
    width: 100%;
}

/* 메뉴 */
.container .buttons .menu-btn{
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 공지사항 */
.container .buttons .announcement-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 리뷰 */
.container .buttons .reviews-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 버튼을 눌렀을때 보여주는 콘텐츠 영역 */
.container .content{
    width: 100%;
}

/* 메뉴 콘텐츠 */
.container .content .menu-content{
    width: 100%;
}

/* 카테고리 슬라이더 부분 */
.container .content .menu-content .category-content{
    width: 100%;
    display: flex; /* 이 부분을 추가합니다 */
    overflow-x: auto; /* 수평 스크롤을 위해 추가합니다 */
    background-color: var(--white);
    scrollbar-width: none;
    position: sticky;
    top: 0; /* 상단에 고정 */
    z-index: 1; /* 다른 요소들 위에 오도록 z-index 설정 */
}

.container .content .menu-content .category-content .category{
    margin: 10px;
    padding: 10px 20px;
    flex: 0 0 auto; /* flex 항목이 자신의 크기를 유지하도록 설정합니다 */
    border-radius: 20px;
    text-align: center;
    background-color: var(--white);
    color:var(--black);
    cursor: pointer; /* 마우스 오버 시 커서 변경을 위해 추가합니다 */
    border: 1px solid var(--gray);
    transition: background-color 0.3s, color 0.3s; /* 부드러운 색상 전환 */
}

.container .content .menu-content .category-content .category.active{
    background-color: var(--gray);
    color: var(--white);
}

.container .content .menu-content .category-content:last-child{
    margin-right: 0;
}

/* 메뉴 리스트 */
.container .content .menu-content .menus-container {
}
.container .content .menu-content .menus-container .category-name{
    padding:var(--padding);
    background-color: var(--extra-light-gray);
    color: var(--gray);
    font-size: var(--font-middle);
    font-weight: bold;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .menu-content .menus-container .menu{
    width: 100%;
    display: flex;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .menu-content .menus-container .menu .image{
    padding: 10px;
    width: 120px;
    height: 120px;
    overflow: hidden;
}
.container .content .menu-content .menus-container .menu .image img{
    width: 100%;
    height: 100%;
}
.container .content .menu-content .menus-container .menu .text{
    padding: 10px;
    display: flex;
    flex-direction: column;
}
.container .content .menu-content .menus-container .menu .text .menu-name{
    margin-bottom: 10px;
    font-size: var(--font-content);
    font-weight: bold;
    color:var(--black);
}

.container .content .menu-content .menus-container .menu .text .menu-info{
    margin-bottom: 10px;
    flex: 1;
    font-size: var(--font-content);
    color:var(--gray);
}

.container .content .menu-content .menus-container .menu .text .menu-price{
    font-size: var(--font-content);
    color:var(--black);
}

/* 공지사항 콘텐츠 */
.container .content .announcement-content{
    width: 100%;
    display: none;
}

.container .content .announcement-content .announcement {
    width: 100%;
    display: flex;
    flex-direction: column;
    padding: 20px;
    border-bottom: 2px solid var(--light-gray);
}

.container .content .announcement-content .announcement .title {
    padding:0;
    font-size: var(--font-middle);
    color: var(--black);
    margin-bottom: 20px;
}
.container .content .announcement-content .announcement .date{
    padding:0;
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .announcement-content .announcement .content{
    padding:0;
    font-size: var(--font-content);
    color: var(--gray);
}

/* 리뷰 */
.container .content .review-content{
    width: 100%;
    display: none;
    padding: var(--padding);
}

.container .content .review-content .review{
    margin-bottom: 20px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    border: 2px solid var(--light-gray);
    padding: var(--padding);
}
.container .content .review-content .review .title{
    padding: 0;
    font-size: var(--font-content);
    color: var(--black);
}
.container .content .review-content .review .mid{
    display: flex;
    font-size: var(--font-small);
    color: var(--light-gray);
    flex: 1;
    padding-bottom: 20px;
}

.container .content .review-content .review .mid .star{
    font-size: var(--font-small); /* 별의 크기 조절 */
    color: #ccc; /* 기본 별 색상 (회색) */
    position: relative; /* 상대적 위치 설정 */
    margin-right: 20px;
}

.container .content .review-content .review .mid .star span.filled {
    color: gold; /* 노란색 별 */
}

.container .content .review-content .review .mid .star span.empty {
    color: #ccc; /* 회색 별 */
}

.container .content .review-content .review .mid .date{}
.container .content .review-content .review .content{
    font-size: var(--font-content);
    color: var(--black);
}

/* 모달 창 스타일 */
.modal {
    display: none; /* 초기에는 숨김 */
    position: absolute; /* 화면 중앙에 위치 */
    z-index: 1; /* 내용 위에 표시 */
    left: 0;
    top: 0;
    width: 100%; /* 전체 너비 */
    height: 100%; /* 전체 높이 */
    overflow: auto; /* 내용이 넘칠 경우 스크롤 */
    background-color: rgb(0,0,0); /* 검은색 배경 */
    background-color: rgba(0,0,0,0.4); /* 어두운 투명도 */
}

/* 모달 내용 스타일 */
.modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 페이지 중앙에 위치 */
    padding: 20px;
    border: 1px solid #888;
    width: 300px; /* 너비 설정 */
    height: 300px; /* 높이 설정 */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.modal-title {
    text-align: center;
    font-weight: bold;
}

.modal-warning {
    color: red;
    text-align: center;
}

.modal-label {
    text-align: left;
}

.modal-input {
    width: 100%;
    height: 100px;
}

.modal-submit {
    background-color: #222; /* 검은색 배경 */
    color: #fff; /* 하얀색 글씨 */
    text-align: center;
}

 

main.js - 여기서 별 구현은 GPT의 힘을 많이 빌림

const menu = document.querySelector(".menu-btn");
const announcement = document.querySelector(".announcement-btn");
const reviews = document.querySelector(".reviews-btn");
const menuContent = document.querySelector('.menu-content');
const announcementContent = document.querySelector('.announcement-content');
const reviewContent = document.querySelector('.review-content');

/* 식당 평점 별 부분*/
document.addEventListener('DOMContentLoaded', function() {
    var ratings = document.querySelectorAll('.ratings');

    ratings.forEach(function(rating) {
        var stars = rating.querySelector('.stars');
        var label = rating.querySelector('label');
        var ratingValue = parseFloat(label.textContent);
        var starPercentage = (ratingValue / 5) * 100; // 평점을 백분율로 변환
        var starPercentageRounded = `${parseFloat(starPercentage.toFixed(1))}%`; // 첫 번째 소수점 자리까지 표시

        // 금색 별을 표시할 span 요소 생성 및 스타일 설정
        var starFill = document.createElement('span');
        starFill.className = 'star-fill';
        starFill.style.width = starPercentageRounded;
        starFill.innerHTML = '★★★★★'; // 금색 별

        // 기존 별점 요소를 클리어하고, 금색 별 span과 기본 별을 추가
        stars.innerHTML = ''; // 기존 내용을 클리어
        stars.appendChild(starFill); // 금색 별 추가
        stars.innerHTML += '★★★★★'; // 기본 별 추가 (회색 별)
    });
});

/* 메뉴 버튼 클릭 시 */
menu.addEventListener('click', () => {
    menu.style.backgroundColor = '#ff7a2f';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#fff';
    announcement.style.color = '#717171';
    reviews.style.color = '#717171';

    menuContent.style.display = 'block';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'none';
});

/* 공지사항 버튼 클릭 시 */
announcement.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = '#ff7a2f';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#717171';
    announcement.style.color = '#fff';
    reviews.style.color = '#717171';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'block';
    reviewContent.style.display = 'none';
});


/* 리뷰 버튼 클릭 시 */
reviews.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = '#ff7a2f';

    menu.style.color = '#717171';
    announcement.style.color = '#717171';
    reviews.style.color = '#fff';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'block';
});

// 신고 버튼 클릭 이벤트
document.querySelector('.report-button').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'block';
});

// 신고하기 버튼 클릭 이벤트로 모달 창 닫기 (옵션)
document.querySelector('.modal-submit').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'none';
});

// 호출하기 버튼 클릭 이벤트
document.querySelector('.call-button').addEventListener('click', () => {
    document.getElementById('callModal').style.display = 'block';
});

// 호출하기 모달에서 호출하기 버튼 클릭 이벤트
document.getElementById('submitCall').addEventListener('click', () => {
    // 실제 애플리케이션에서는 이곳에 선택된 옵션을 처리하는 로직을 구현합니다.
    // 예: 선택된 라디오 버튼의 값을 서버로 전송
    console.log('호출 옵션:', document.querySelector('input[name="callOption"]:checked').value);
    document.getElementById('callModal').style.display = 'none'; // 모달 닫기
});

// 모달 창 밖을 클릭할 때 모달 창 닫기
window.addEventListener('click', e => {

    if (e.target.classList.contains('modal')) {
        e.target.style.display = 'none';
    }
});


// 슬라이더 부분
document.addEventListener('DOMContentLoaded', function() {
    const slider = document.querySelector('.category-content');
    let isDown = false;
    let startX;
    let scrollLeft;

    slider.addEventListener('mousedown', (e) => {
        isDown = true;
        slider.classList.add('active');
        startX = e.pageX - slider.offsetLeft;
        scrollLeft = slider.scrollLeft;
    });

    slider.addEventListener('mouseleave', () => {
        isDown = false;
        slider.classList.remove('active');
    });

    slider.addEventListener('mouseup', () => {
        isDown = false;
        slider.classList.remove('active');
    });

    slider.addEventListener('mousemove', (e) => {
        if(!isDown) return;
        e.preventDefault();
        const x = e.pageX - slider.offsetLeft;
        const walk = (x - startX) * 3; //scroll-fast
        slider.scrollLeft = scrollLeft - walk;
    });
});

document.addEventListener('DOMContentLoaded', function() {
    const categories = document.querySelectorAll('.category');
    const menuContainers = document.querySelectorAll('.menu-category');
    const categoryContent = document.querySelector('.category-content');

    // 현재 활성화된 카테고리를 표시하고 스크롤 시 갱신
    function setActiveCategory() {
        let currentActiveIndex = 0;
        menuContainers.forEach((container, index) => {
            const containerTop = container.getBoundingClientRect().top;
            if (containerTop - window.innerHeight / 2 < 0) {
                currentActiveIndex = index;
            }
        });

        categories.forEach((category, index) => {
            if (index === currentActiveIndex) {
                category.classList.add('active');
            } else {
                category.classList.remove('active');
            }
        });
    }

    // 클릭한 카테고리가 화면에 완전히 보이지 않을 경우 스크롤
    function ensureCategoryVisible(category) {
        const categoryRect = category.getBoundingClientRect();
        const containerRect = categoryContent.getBoundingClientRect();

        if (categoryRect.left < containerRect.left) {
            // 카테고리 버튼이 뷰포트 왼쪽 밖에 위치한 경우
            categoryContent.scrollLeft -= (containerRect.left - categoryRect.left) + 20; // 여백 추가
        } else if (categoryRect.right > containerRect.right) {
            // 카테고리 버튼이 뷰포트 오른쪽 밖에 위치한 경우
            categoryContent.scrollLeft += (categoryRect.right - containerRect.right) + 20; // 여백 추가
        }
    }

    // 카테고리 클릭 이벤트
    categories.forEach(category => {
        category.addEventListener('click', function() {
            const targetId = this.getAttribute('data-target');
            const targetElement = document.getElementById(targetId);

            if (targetElement) {
                // category-content의 높이를 가져옵니다.
                const categoryContentHeight = document.querySelector('.category-content').offsetHeight;

                // targetElement까지의 절대 위치를 계산합니다.
                const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;

                // category-content의 높이만큼 위치를 조정합니다.
                const offsetPosition = elementPosition - categoryContentHeight;

                // 계산된 위치로 스크롤합니다.
                window.scrollTo({
                    top: offsetPosition,
                    behavior: "smooth"
                });
            }
        });
    });

    // 스크롤 이벤트
    window.addEventListener('scroll', setActiveCategory);

    // 초기 활성화 카테고리 설정
    setActiveCategory();
});


// 리뷰 별점 정수값을 별 개수로 표시하는
document.addEventListener('DOMContentLoaded', function() {
    const reviewStars = document.querySelectorAll('.review .star');

    reviewStars.forEach(function(star) {
        const rating = parseInt(star.getAttribute('data-rating'));
        let starsText = '★★★★★';

        // 노란색 별을 표시할 부분과 회색 별을 표시할 부분을 결정
        let filledStars = starsText.slice(0, rating).replace(/★/g, '<span class="filled">★</span>');
        let emptyStars = starsText.slice(rating).replace(/★/g, '<span class="empty">★</span>');

        // 별점을 HTML로 설정
        star.innerHTML = filledStars + emptyStars;
    });
});

 

info.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/info.css" rel="stylesheet" type="text/css">
    <script src="/js/user/info.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/info" method="post" class="info-form">
            <div class="menu-image">
                <img src="/images/sample.png" alt="음식 이미지" class="menu-img">
            </div>
            <div class="info">
                <div class="info-title">짜파게티</div>
                <div class="info-contents">맛있는 짜파게티 김치 미포함</div>
                <div class="info-price">300원</div>
            </div>
            <div class="option">
                <div class="option-title">
                    <div class="option-title-text">매운맛</div>
                    <div class="option-title-select">필수</div>
                </div>
                <div class="option-contents">
                    <div class="option-contents-select">
                        <input type="radio" name="spiciness" class="option-check" value="spicy">
                        <span class="option-contents-content">맵게 해주세요</span>
                        <span class="option-contents-price">+3000원</span>
                    </div>
                    <div class="option-contents-select">
                        <input type="radio" name="spiciness" class="option-check" value="not-spicy">
                        <span class="option-contents-content">안맵게 해주세요</span>
                        <span class="option-contents-price">+4000원</span>
                    </div>
                </div>
            </div>
            <div class="option">
                <div class="option-title">
                    <div class="option-title-text">토핑</div>
                    <div class="option-title-select">선택</div>
                </div>
                <div class="option-contents">
                    <div class="option-contents-select">
                        <input type="checkbox" class="option-check">
                        <span class="option-contents-content">치즈</span>
                        <span class="option-contents-price">1000원</span>
                    </div>
                    <div class="option-contents-select">
                        <input type="checkbox" class="option-check">
                        <span class="option-contents-content">감자</span>
                        <span class="option-contents-price">1000원</span>
                    </div>
                </div>
            </div>
            <div class="white-gap"></div>
            <div class="count">
                <div class="count-inner">
                    <div class="count-text">수량</div>
                    <div class="count-button">
                        <input type="button" class="minus" value="-">
                        <div class="text">1</div>개
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
                <input type="button" class="submit button" value="300원 담기" onclick="infosubmit(form)">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

info.css

@charset "UTF-8";

.container form{
    width: 100%;
    margin: 0;
    padding: 0;
}
/* 메인 사진 */
.container .menu-image{
    width: 100%;
    height: 250px;
    overflow: hidden;
}

.container .menu-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 음식 설명 */
.container .info{
    padding: var(--padding);
    margin: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

.container .info .info-title{
    font-size: var(--font-big);
    font-weight: bold;
    margin-bottom: 10px;
    color: var(--black);
}

.container .info .info-contents{
    font-size: var(--font-small);
    color: var(--light-gray);
}

.container .info .info-price{
    font-size: var(--font-middle);
    color: var(--black);
    text-align: right;
}

/* 옵션 */
.container .option{
    margin: 0;
    display: flex;
    flex-direction: column;
}

.container .option .option-title{
    padding: var(--padding);
    width: 100%;
    display: flex;
    justify-content: space-between;
    background-color: var(--extra-light-gray);
    color: var(--light-gray);
}

.container .option .option-title .option-title-text{
    padding: 10px;
    color: var(--gray);
}

.container .option .option-title .option-title-select{
    padding: 10px;
    background-color: var(--light-gray);
    color: var(--gray);
}

.container .option .option-title .option-title-select .option-check{
    margin-right: 10px;
}

.container .option .option-title .option-title-select .option-contents-content{
    margin: 10px 0;
}

.container .option .option-contents{
    padding: var(--padding);
    width: 100%;
    display: flex;
    flex-direction: column;
}

.container .option .option-contents .option-contents-select{
    display: flex;
    margin-bottom: 10px; /* 마지막 요소를 제외하고 각 요소의 하단에 간격 추가 */
}

.container .option .option-contents .option-contents-select:last-child {
    margin-bottom: 0; /* 마지막 요소는 하단 간격 제거 */
}

.container .option .option-contents .option-contents-select .option-contents-content{
    flex: 1;
}

/* count 때문에 넣는 빈공간 */
.container .white-gap{
    width: 100%;
    height: var(--fixedBtn-height);
}

/* 수량 */
.container .count {
    position: fixed;
    left: 50%;
    bottom: 0;
    margin: 0;
    /* 자신의 너비의 반만큼 왼쪽으로 이동하여 정확히 중앙에 위치하도록 조정 */
    transform: translateX(-50%);
    width: var(--innerBox-width);
    background-color: white;
    z-index: 1; /* 필요에 따라 조정 */
    padding: var(--padding);
}
.container .count .count-inner{
    display: flex;
    justify-content: space-between; /* 내부 요소를 양 끝으로 정렬 */
    width: 100%; /* 부모 컨테이너의 전체 너비 사용 */
    padding: 0 20px; /* 좌우 패딩 추가로 내부 요소들 사이 간격 조정 */
    margin-bottom: 10px;
}
.container .count .count-inner .count-text {
    font-size: var(--font-content); /* 필요에 따라 조정 */
    color: var(--black);
    display: flex;
    justify-content: center;
    align-items: center;
}

.container .count .count-inner .count-button {
    padding: 5px;
    display: flex;
    justify-content: right;
    border: 2px solid var(--light-gray);
}

.container .count .count-inner .count-button .minus,
.container .count .count-inner .count-button .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container .count .count-inner .count-button .text{
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 버튼 */
.container .count .submit{
    width: calc(100% - 20px); /* 패딩 고려하여 너비 조정 */
}

info.js

/* form 하기 전에 체크 */

function infosubmit(form) {
    form.submit();
}

const ocs = document.querySelectorAll('.option-contents-select');

ocs.forEach(select => {
    select.addEventListener('click', e => {
        // 'this' 대신 'select' 사용
        const input = select.querySelector('input[type=checkbox], input[type=radio]');
        if (e.target !== input) {
            input.checked = !input.checked;
            // 라디오 버튼의 경우, 다른 라디오 버튼의 상태 변경을 위해 이벤트를 발생시킵니다.
            input.dispatchEvent(new Event('change'));
        }
    });
});

 

이제 메인은 반응형 하고 값 제대로 안들어갈때나 건드릴듯 

기능 참 많네 

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 8일차  (0) 2024.04.06
[Spring/AWS] 팀프로젝트 7일차  (0) 2024.04.04
[Spring/AWS] 팀프로젝트 5일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 3일차  (0) 2024.03.29

오늘은 이번주 금요일 중간 발표를 위해 보여지는 페이지를 먼저 구현하기로 했습니다.

 

손님이 처음 보는 메뉴 메인 페이지 구현

상단에 가게 메인 이미지

두번째 타이틀 신고 버튼

세번째 평점 평균, 리뷰

네번재 호출하기 버튼

다섯번째 메뉴 공지 리뷰 버튼 해당 버튼을 클릭하면 여섯번째에 거기에 해당하는 컨텐츠를 보여준다

메뉴 버튼을 클락하면 나오는 메뉴 컨텐츠의 첫번째는 카테고리를 보여주고 두번째는 메뉴를 카테고리로 분류해서 세로로 보여준다

보여주기 전에 더미 db 파일이 필요하다

package kr.ganjuproject.repository.dumi;

import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import kr.ganjuproject.entity.Category;
import kr.ganjuproject.entity.Menu;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;

@Profile("local")
@Component
@RequiredArgsConstructor
public class InitMenuDB {
    private final InitMenuService initMenuService;
    @PostConstruct  // 생성자 실행되면 바로 호출
    public void init() {
        initMenuService.init();
    }
    @Component
    static class InitMenuService {
        @Autowired
        EntityManager em;
        @Transactional
        public void init() {
            // 카테고리 데이터 생성
            String[] categoryNames = {"한식", "중식", "일식", "양식", "분식", "디저트"};
            for (int i = 0; i < categoryNames.length; i++) {
                Category category = new Category(null, i + 1, categoryNames[i], new ArrayList<>());
                em.persist(category);

                // 각 카테고리에 메뉴 추가
                for (int j = 0; j < 5; j++) {
                    Menu menu = new Menu();
                    menu.setName(category.getName() + " 메뉴 " + (j + 1));
                    menu.setPrice((j + 1) * 1000); // 가격 설정 예시
                    menu.setCategory(category); // 생성된 카테고리에 메뉴 설정
                    // 메뉴에 대한 추가적인 설정...

                    em.persist(menu);

                    // 카테고리 객체에 메뉴 객체 연결 (양방향 관계 설정)
                    category.getMenus().add(menu);
                }
            }
            System.out.println("더미 데이터 생성 완료");
        }
    }
}

이렇게 서버를 시작할때 더미데이터를 만드려면

@Profile("local")

이 부분과 yml 파일에

spring:
  output:
    ansi:
      enabled: always
  profiles:
    active: local

active 를 지정해줘야 한다

어쩌다보니 기능까지 같이 구현해 버림

메인 더미데이터로 카테고리 슬라이더 메뉴 펼치기 공지사항 더미 리뷰 더미

그리고 메뉴 클릭시 페이지링크 까지 구현

오늘 작성한 코드 변경시 참고하기위해 남깁니다

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
    <script src="/js/user/main.js" type="text/javascript" defer></script>

    <!-- 슬릭 슬라이더 관련 -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>

    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css" />
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css" />
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <img src="/images/sample.png" alt="식당 메인 이미지" class="restaurant-image">
        <div class="title">
            <div class="restaurant-name">식당 이름</div>
            <button class="report-button button">신고</button>
        </div>
        <div class="ratings-reviews">
            <div class="ratings">
                <div class="stars">★★★★☆</div>
                <label>0.0</label>
            </div>
            <button class="review-button button">리뷰</button>
        </div>
        <div class="call">
            <button class="call-button button">호출하기</button>
        </div>
        <div class="buttons">
            <button class="menu-btn button">메뉴</button>
            <button class="announcement-btn button">공지사항</button>
            <button class="reviews-btn button">리뷰</button>
        </div>
        <div class="content">
            <div class="menu-content">
                <div class="category-content">
                    <div th:each="category : ${categories}" class="category">
                        <div th:text="${category.name}" class="category-name"></div>
                    </div>
                </div>
                <div class="menus-container">
                    <div th:each="category : ${categories}" class="menu-category" th:data-category-id="${category.id}">
                        <div th:text="${category.name}" class="category-name"></div>
                        <div class="menus" th:each="menu : ${menus}">
                            <div th:if="${menu.category.id} == ${category.id}" class="menu" onclick="location.href='/menu/info?id=${menu.id})}'">
                                <div class="image"><img src="/images/sample.png" alt="메뉴 이미지" class="restaurant-image"></div>
                                <div class="text">
                                    <div th:text="${menu.name}" class="menu-name"></div>
                                    <div th:text="${menu.info}" class="menu-info"></div>
                                    <div th:text="${menu.price}" class="menu-price"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="announcement-content">
                <div class="announcement">
                    <div class="title">공지사항1</div>
                    <div class="date">2024.04.01</div>
                    <div class="content">쉬고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항2</div>
                    <div class="date">2024.04.02</div>
                    <div class="content">여행 가고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항3</div>
                    <div class="date">2024.04.03</div>
                    <div class="content">아플것 같아서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항4</div>
                    <div class="date">2024.04.04</div>
                    <div class="content">그냥 쉽니다</div>
                </div>
            </div>
            <div class="review-content" >
                <div class="review">
                    <div class="title">박윤재</div>
                    <div class="mid">
                        <div class="star">★★★★★</div>
                        <div class="date">16:00</div>
                    </div>
                    <div class="content">음식이 친절하고 사장님이 맛있어요</div>
                </div>
                <div class="review">
                    <div class="title">손지영</div>
                    <div class="mid">
                        <div class="star">★★★☆☆</div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">음 괜찮네요</div>
                </div>
                <div class="review">
                    <div class="title">윤경재</div>
                    <div class="mid">
                        <div class="star">☆☆☆☆☆</div>
                        <div class="date">2023.12.31</div>
                    </div>
                    <div class="content">집에 보내주세요</div>
                </div>
            </div>
        </div>
    </div>
    <footer></footer>
    <!-- 모달 창 -->
    <div id="reportModal" class="modal">
        <div class="modal-content">
            <h2 class="modal-title">가게 신고</h2>
            <p class="modal-warning">가게 신고 시 돌이킬 수 없으며,<br>허위로 신고를 작성한 경우<br>신고는 무효화 됩니다.</p>
            <label for="reportReason" class="modal-label">신고 사유</label>
            <textarea id="reportReason" class="modal-input"></textarea>
            <button class="modal-submit button">신고하기</button>
        </div>
    </div>
</div>
</body>
</html>
info.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" xmlns="http://www.w3.org/1999/html"
      xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/menu/info" method="post" class="info-form">
            <img src="/images/sample.png" alt="음식 이미지" class="menu-image">
            <div class="info">
                <div class="title">짜파게티</div>
                <div class="info">맛있는 짜파게티 김치 미포함</div>
                <div class="info">300원</div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="title-text">매운맛</div>
                    <div class="title-option">필수</div>
                </div>
                <div class="contents">
                    <div class="option">
                        <input type="radio" class="d">
                        <p>맵게 해주세요</p>
                        <p>3000원</p>
                    </div>
                    <div class="option">
                        <input type="radio" class="d">
                        <p>맵게 해주세요</p>
                        <p>3000원</p>
                    </div>
                </div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="title-text">토핑</div>
                    <div class="title-option">선택</div>
                </div>
                <div class="contents">
                    <div class="option">
                        <input type="radio" class="d">
                        <p>계란</p>
                        <p>1000원</p>
                    </div>
                    <div class="option">
                        <input type="radio" class="d">
                        <p>치즈</p>
                        <p>500원</p>
                    </div>
                </div>
            </div>
            <div class="count">
                <div class="count-text">수량</div>
                <div class="count-button">
                    <input type="button" class="minus">-</input>
                    <div class="text">1</div>개
                    <input type="button"  class="plus">+</input>
                </div>
            </div>
            <input type="button" class="submit">300원 담기</input>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

order.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/menu/order" method="post">
            <div class="restaurant-name">식당 이름</div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">음식이름</div>
                        <div class="menu-price">가격 : 5,000원</div>
                        <div class="option">옵션 : 안맵게 해주세요(-3000원)</div>
                        <div class="total-price">3000원</div>
                    </div>
                </div>
                <div class="num">
                    <button class="minus">-</button>
                    <div class="text"></div>
                    <button class="plus">+</button>
                </div>
            </div>
            <div class="menuplus">
                +메뉴추가
            </div>
            <div class="request">
                <div class="title">요청사항</div>
                <textarea class="contents" id="contents" name="contents" placeholder="내용을 입력하세요" rows="3">1</textarea>
                <button class="submit">3000원 주문하기</button>
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

main.css

@charset "UTF-8";

/* 메인 이미지 */
.container .restaurant-image{
    width: 100%;
    height: 200px;
    object-fit: cover;
    object-position: center top;
}
/* 타이틀과 신고버튼 */
.container .title{
    width: 100%;
    padding: 0 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.container .title .restaurant-name{
    font-weight: bold;
}
.container .title .report-button{
    background-color: transparent;
    color: red;
    border: none;
    font-size: 16px;
}
/* 별표와 리뷰 버튼*/
.container .ratings-reviews{
    padding: 0 10px;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.container .ratings-reviews .ratings{
    display: flex;
}
.container .ratings-reviews .review-button{}

/* 호출 버튼 */
.container .call{
    padding: 0 10px;
    width: 100%;
}
.container .call .call-button {
}

/* 메뉴 공지사항 리뷰 */
.container .buttons{
    border-bottom: 3px solid var(--orange);
    display: flex;
    width: 100%;
}

/* 메뉴 */
.container .buttons .menu-btn{
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 공지사항 */
.container .buttons .announcement-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 리뷰 */
.container .buttons .reviews-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 버튼을 눌렀을때 보여주는 콘텐츠 영역 */
.container .content{
    width: 100%;
}

/* 메뉴 콘텐츠 */
.container .content .menu-content{
    width: 100%;
}

/* 카테고리 슬라이더 부분 */
.container .content .menu-content .category-content{
    margin: auto;
    width: 90%;
}
.container .content .menu-content .category-content .category{
    margin: 10px;
    border-radius: 20px;
    text-align: center;
    background-color: white;
}
.container .content .menu-content .category-content .category:hover{
     transition: 1s;
     background-color: gray;
 }

/* 메뉴 리스트 */
.container .content .menu-content .menus-container {

}
.container .content .menu-content .menus-container .category-name{}
.container .content .menu-content .menus-container .menu{
    width: 100%;
    display: flex;
    border: 1px solid red;
}
.container .content .menu-content .menus-container .menu .image{
    width: 70px;
    height: 70px;
    overflow: hidden;
}
.container .content .menu-content .menus-container .menu .image img{
    width: 70px;
    height: 70px;
}
.container .content .menu-content .menus-container .menu .text{
    display: flex;
    flex-direction: column;
}
.container .content .menu-content .menus-container .menu .text .menu-name{}
.container .content .menu-content .menus-container .menu .text .menu-info{}
.container .content .menu-content .menus-container .menu .text .menu-price{}
/* 공지사항 콘텐츠 */
.container .content .announcement-content{
    width: 100%;
    display: none;
}

.container .content .announcement-content .announcement {
    width: 100%;
    height: 100px;
    display: flex;
    flex-direction: column;
    padding: 10px;
}
.container .content .announcement-content .announcement .title {
    font-size: var(--font-big);
    color: var(--black);

}
.container .content .announcement-content .announcement .content{
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .announcement-content .announcement .date{
    font-size: var(--font-middle);
    color: var(--gray);
}

/* 리뷰 */
.container .content .review-content{
    width: 100%;
    display: none;
}

.container .content .review-content .review{
    margin: 10px;
    padding: 10px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}
.container .content .review-content .review .title{
    font-size: var(--font-big);
    color: var(--black);
}
.container .content .review-content .review .mid{
    display: flex;
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .review-content .review .mid .star{}
.container .content .review-content .review .mid .date{}
.container .content .review-content .review .content{
    font-size: var(--font-middle);
    color: var(--gray);
}

/* 모달 창 스타일 */
.modal {
    display: none; /* 초기에는 숨김 */
    position: absolute; /* 화면 중앙에 위치 */
    z-index: 1; /* 내용 위에 표시 */
    left: 0;
    top: 0;
    width: 100%; /* 전체 너비 */
    height: 100%; /* 전체 높이 */
    overflow: auto; /* 내용이 넘칠 경우 스크롤 */
    background-color: rgb(0,0,0); /* 검은색 배경 */
    background-color: rgba(0,0,0,0.4); /* 어두운 투명도 */
}

/* 모달 내용 스타일 */
.modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 페이지 중앙에 위치 */
    padding: 20px;
    border: 1px solid #888;
    width: 300px; /* 너비 설정 */
    height: 300px; /* 높이 설정 */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.modal-title {
    text-align: center;
    font-weight: bold;
}

.modal-warning {
    color: red;
    text-align: center;
}

.modal-label {
    text-align: left;
}

.modal-input {
    width: 100%;
    height: 100px;
}

.modal-submit {
    background-color: #222; /* 검은색 배경 */
    color: #fff; /* 하얀색 글씨 */
    text-align: center;
}

main.js

const menu = document.querySelector(".menu-btn");
const announcement = document.querySelector(".announcement-btn");
const reviews = document.querySelector(".reviews-btn");
const menuContent = document.querySelector('.menu-content');
const announcementContent = document.querySelector('.announcement-content');
const reviewContent = document.querySelector('.review-content');

menu.addEventListener('click', () => {
    menu.style.backgroundColor = '#ff7a2f';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#fff';
    announcement.style.color = '#717171';
    reviews.style.color = '#717171';

    menuContent.style.display = 'block';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'none';
});

announcement.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = '#ff7a2f';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#717171';
    announcement.style.color = '#fff';
    reviews.style.color = '#717171';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'block';
    reviewContent.style.display = 'none';
});

reviews.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = '#ff7a2f';

    menu.style.color = '#717171';
    announcement.style.color = '#717171';
    reviews.style.color = '#fff';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'block';
});

// 신고 버튼 클릭 이벤트
document.querySelector('.report-button').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'block';
});

// 신고하기 버튼 클릭 이벤트로 모달 창 닫기 (옵션)
document.querySelector('.modal-submit').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'none';
});

// 슬릭 슬라이더
$('.category-content').slick({
    dots: false,
    infinite: false,
    speed: 300,
    slidesToShow: 3
});

// 카테고리를 클릭 했을 때 해당 메뉴 부분으로 가능 기능
document.addEventListener('DOMContentLoaded', function() {
    const categories = document.querySelectorAll('.category');

    categories.forEach(category => {
        category.addEventListener('click', function() {
            const categoryId = this.dataset.categoryId;
            scrollToMenuCategory(categoryId);
        });
    });

    function scrollToMenuCategory(categoryId) {
        const menuCategory = document.querySelector(`.menu-category[data-category-id='${categoryId}']`);
        if (menuCategory) {
            menuCategory.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    }
});

MenuController.java

package kr.ganjuproject.controller;

import kr.ganjuproject.entity.Category;
import kr.ganjuproject.entity.Menu;
import kr.ganjuproject.service.CategoryService;
import kr.ganjuproject.service.MenuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Optional;

@Controller
@Slf4j
@RequestMapping("/menu")
@RequiredArgsConstructor
public class MenuController {

    private final MenuService menuService;
    private final CategoryService categoryService;

    @GetMapping("/main")
    public String main(Model model) {
        List<Category> categories = categoryService.getList();
        model.addAttribute("categories", categories);
        List<Menu> menus = menuService.getList();
        model.addAttribute("menus", menus);
        return "menu/main";
    }

    @GetMapping("/info/{id}")
    public String info(@PathVariable Long id, Model model){
        Optional<Menu> menu = menuService.findById(id);

        if(menu.isPresent()) {
            Menu m = menu.get();
            model.addAttribute("menu", m);
            return"menu/info";
        }else{
            return "redirect:/menu/main";
        }
    }
    @GetMapping("/cart")
    public String cart(){
        return"menu/cart";
    }
    @GetMapping("/order")
    public String order(){
        return"menu/order";
    }
    @GetMapping("/review")
    public String review(){
        return"menu/review";
    }
}

MenuService.java

package kr.ganjuproject.service;

import kr.ganjuproject.entity.Menu;
import kr.ganjuproject.repository.MenuRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MenuService {

    private final MenuRepository menuRepository;

    public List<Menu> getList() {
        return menuRepository.findAll();
    }

    public Optional<Menu> findById(Long id){return menuRepository.findById(id);}
}

MenuRepository.java

package kr.ganjuproject.repository;

import kr.ganjuproject.entity.Menu;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MenuRepository extends JpaRepository<Menu, Long> {
}

CategoryService.java

package kr.ganjuproject.service;

import kr.ganjuproject.entity.Category;
import kr.ganjuproject.repository.CategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CategoryService {
    private final CategoryRepository categoryRepository;

    public List<Category> getList(){
        return categoryRepository.findAll();
    }
}

어제는 결재 테스트 한다고 아무것도 못한 느낌이 었는데 오늘은 할만했

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 7일차  (0) 2024.04.04
[Spring/AWS] 팀프로젝트 6일차  (0) 2024.04.03
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 3일차  (0) 2024.03.29
[Spring/AWS] 팀프로젝트 2일차  (0) 2024.03.28

Project Momizi

프로젝트 계획 및 시작

기획 :  HTML, CSS, JavaScript를 배우고 나중에도 활용될 수 있는 프로젝트를 생각하다가 
        팬게임을 모아둔 사이트를 제작하게 되었습니다.
        미니 게임은 총 3가지 제작 예정입니다. ( 카드 짝 맞추기, 수박 게임, 반응속도 테스트) 추후 추가 예정

        주말에 시간이 생겨 두가지 게임을 추가함

 

깃허브 링크 https://github.com/PARK-Yunjae/ProjectMomizi

 

사이트 링크 https://park-yunjae.github.io/ProjectMomizi/

 

기획


일정 - 2024. 01. 23 ~ 2024. 01. 28 ( 6일 )
    a. 1일차 : 전체적인 일정 기획및 스토리 보드와 css js가 올라갈 html 태그 작성
    b. 2일차 : 사이트의 전체적인 구조 (css 적용) 만들기
    c. 3일차 : 카드짝맞추기 제작
    d. 4일차 : 슈팅게임 제작 - 취소 
    e. 5일차 : 수박게임 제작
    f. 6일차 : 반응형 웹페이지 (모바일 예정)


디자인


페이지 구성
  메인 페이지 , 게임1, 게임2, 게임3

  1. 메인 페이지 ( index ) 
    header - 로고(홈) , 게임1, 게임2, 게임3 링크
    main   - 움직이는 이미지
    footer - 사이트 링크

  2. 게임1 - 카드짝 맞추기 - 기존 예제 참고
    header - 메인페이지 header고정
    main   - 게임 페이지
    
  4. 게임2 - 수박게임
    페이지는 게임1과 동일
    이미지 10종류 - 카드게임 재활용
    
  4. 게임3 - 반응속도 테스트
    페이지는 게임1과 동일
    이미지 10종류 - 카드게임 재활용
    
  4. 게임4 - 리듬 게임 ( 취소 )
    페이지는 게임1과 동일
    이미지 10종류 - 카드게임 재활용


개발

html css 로 화면 구성을 만든 다음 개임 개발 진행 후 반응으로 구현.

1. 메인 페이지 ( 움직이는 canvas 를 배경화면으로 )    
    반응 형 및 사이트, 게임 화면 링크 생성

2. 게임1 카드 짝 맞추기

  카드 짝 맞추기 구현시 순서도를 안하고 개발을 시작해 여러가지 에로사항이 생겼습니다
  그때 그때 필요한걸 짜다보니 변수도 많이 추가되고 게임 재시작을 해야 할때 여러군데서 수정을 해야 했습니다.

 

카드짝맞추기_순서도


3. 게임2 수박게임

   이번에는 순서도를 먼저 만들고 개발에 엔진을 활용할 겁니다.
   matter.js 엔진은 어렵습니다.
   터치 이벤트 분리 시도 3차 실패 후 침 

   어쨋든 완성 하긴 함

 

수박게임 순서도

 

4. 게임3 반응속도 테스트

    의외로 setInterval 하고 setTimeout을 클리어 안해주면 막 클릭했을때 버그 발생해서 수정작업이 조금 발생

 

반응속도테스트 순서도

 

5. 게임4 퍼즐 게임

    grid를 쓰면 의외로 보드 제작은 어렵지 않았으나
    드래그 이벤트 시 서로 객체만 바꾸는게 생각보다 많이 어려웠음

 

퍼즐게임 순서도

 

6. 애니팡?
    리펙토링을 해보기로함 jquery -> vanila javascript 

 

애니팡 순서도

테스트
페이지 제작하면서 자체 테스트와 사이크 주소 배포를 통한 피드백 받으면서 진행 합니다.
배포
깃을 통해 서버에 올려 디자이너분에게 제공후 공개 요청

 

 

 

http css javascript를 배우고 만들게 된 사이트

 

즐겨보던 게임 방송인 겸 프리랜서 디자이너분이 있는데 은혜를 입어 보답하고자 이모티콘이나 이미지를 허락받고 미니게임 사이트를 만들게 됨

 

직접 사이트 피드백고 받고 생각보다 좋은 시간이었음

 

이 사이트로 작은 이벤트도 하고

 

여기에 랭크 기록도 해달라는 요청이 있었는데 db 저장은 안되서 다음을 기약한게 좀 아쉬운 점이었음

 

2024.04.01 추가

나중에 새로운 게임을 만들어 보고싶은 초석이 된 사이트 
뭘 만들면 좋을까 tts와 ai를 이용한 게 재미있을것 같은

 

발표 영상 링크

https://youtu.be/u925ZkJL9BM

 

- YouTube

 

www.youtube.com

 

참고 사이트

 

카드게임

https://velog.io/@sypear/JavaScript-%EB%AF%B8%EB%8B%88%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B9%B4%EB%93%9C-%EC%A7%9D-%EB%A7%9E%EC%B6%94%EA%B8%B0

 

[JavaScript] 미니게임 프로젝트 | 카드 짝 맞추기🃏

HTML, CSS, JavaScript를 이용하여 만든 카드 짝 맞추기 게임

velog.io

 

수박게임

https://www.youtube.com/watch?v=LZvEDigv0Ww

 

애니팡

https://chanung.tistory.com/81

 

자바로 애니팡 게임 만들기

자바 프로젝트로 스윙을 이용한 간단한 애니팡게임을 구현해 보았습니다 아직 많이 부족하고 미숙하고 고쳐야할점이 많습니다. 제작기간은 10일 정도 걸린걱 같고 정리까지 2주 정도 걸린거 같

chanung.tistory.com

 

퍼즐게임

https://www.youtube.com/watch?v=iTBZdg7tg-w

 

 

반응속도는 그냥 했었고

 

애니팡은 저게 아니고 코드 어쩌고 하는 외국 사이트였는데 기억이 안남

'공부 > Project' 카테고리의 다른 글

[Spring, AWS] Ganju Project  (0) 2024.04.23
[JSP/SQL] 프로젝트 EYEVEL  (0) 2024.03.25
[JAVA] 스도쿠 만들기 (Swing)  (0) 2024.03.20

+ Recent posts