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

 

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

상단에 가게 메인 이미지

두번째 타이틀 신고 버튼

세번째 평점 평균, 리뷰

네번재 호출하기 버튼

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

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

보여주기 전에 더미 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

+ Recent posts