TIL - 19주차 코드

 

1. Vue.js, NodeJS : 230403

1) Vue.js의 API 정리

  • 함수에게 전달되는 옵션이라서 ‘Options API’라고 부른다.


  • Global API : Global API은 create를 통해서 전달되는 인자를 API를 통해서 전달된다.


  • Built-ins API는 지시자를 사용하는 API이다.


  • 1차 프로젝트는 원래, Vue.js에서 Options API, Global API, Built-ins API만 사용하려고 했다. 이 3가지 API가 Vue.js에서 제공해주는 기본 API이다. 공식 레퍼런스에 있는 나머지 API는 Vue에서 기본적으로 제공해주는 API가 아니다.


  • Fetch API, XHR에 관한 AJAX 방법론은 JS의 브라우저(플랫폼)에서 기본으로 제공해주는 것이다.



2) insert한 메뉴 업데이트하여 View단에서 다시 보기

  • 첫 번째 방법: 전체 목록을 다시 받아와서 로딩하기

  • 두 번째 방법: id만 반환해서 하나만 다시 요청해서 그것만 로딩하기.

  • 세 번째 방법: lastMenu를 Menu 객체로 얻어와서 사용해서 얻어온다.


  • 결론 : 일관적인 방법은 페이저 방식에서 1페이지와 2페이지 구분이 제대로 안 되어서 목록 전체를 다시 받아오는 방법이 맞는 방법이다.
    • 하지만, 성능적 이슈나 사용자의 요구사항이 있다면 두 번째 방법이나 세 번째 방법을 이용한다.


  • 만약에, JSON 타입으로 요청 데이터를 보냈으면 insert하는 AJAX의 반환 타입도 JSON 타입으로 바꿔줘야 한다. 하지만, 이번엔 text 타입으로 요청 데이터를 보냈다.
  • 추가로 이클립스에서 코드를 자동 정렬하는 방법 : cmd + acmd + shift + f


a. 실습 코드 :

  • MenuController.java
troller.api;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;
import kr.co.rland.web.service.MenuService;

// API 이용하기 = AJAX


@RestController("apiMenuController")
@RequestMapping("menus")	// 예전에는 절대경로로 썼지만 지금은 "/"를 안써도 된다. 
public class MenuController {
	
	// REST API에서는 반환값이 문서를 전달하는 것이 아니라 사용자가 받는 데이터이다. 
	// List<MenuView>
//	public List<MenuView> getList(){
//		
//	}
	
	@Autowired
	private MenuService service;
	
	// insert는 아직 넣어줄 데이터가 없어서 문제가 많다.
	// 요청한 데이터를 @RequestBody로 매핑하면, 헤더를 정해줘야 한다.(타입!!정해주기 )
	@PostMapping
	public String insert(@RequestBody Menu menu) {
		
		menu.setCategoryId(1);
		
		int addCount = service.add(menu);
		System.out.println(addCount);
		
		System.out.println(menu.getName());
		
		return "ok";	// 다양한 방법의 반환 방법!도 있다.
	}
	
	
}

  • list.js
	methods: {
		load() {
			// ========================================================================================
			// XHR(XmlHttpRequest)
			// - Callback(나중에 호출한다 =  delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
			//  이용한 비동기처리

			// Fetch API 
			// - Promise를 이용한 비동기 처리

			// ========================================================================================
			// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****

			let promise = fetch("/menus");

			promise
				.then(response => {
					// console.log("도착 했냐?");
					// console.log(response.json());
					return response.json();	// 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다. 
				})
				.then(list => {
					this.list = list;	// 기존의 데이터를 대치하는 것이라서 추가하는 것이 아니다.
					console.log(this);
					// 데이터를 추가하려면 이렇게 하면 안된다. 
					// this.list는 모델에 담는 것이다. list는 넘겨 받은 데이터이다.
				})
				.catch(error => {
					console.log("error"); // 중간에 url의 요청한 데이터가 반환 에러가 발생하면 점프해서 에러 반환!! 
				});						  // then은 이전의 내용을 중첩해서 사용한다.

			// ========================================================================================

			// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
			//			let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
			//			let list = await response.json();	// 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
			//			console.log(list);		

			// ========================================================================================


			// ========================================================================================

		}
	},


	beforeCreate() { console.log("beforeCreate") },
	created() { console.log("created") },
	beforeMount() { console.log("beforeMount") },
	mounted() {
		console.log("mounted")
		this.load();
		console.log(this);
	},
	beforeUpdate() { console.log("beforeUpdate") },
	updated() { console.log("updated") },
	beforeUnmount() { console.log("beforeUnmount") },
	unmounted() { console.log("unmounted") }


})
.mount("#main-section");



2) detail 페이지도 Vue.js로 만들 때, 주의할 사항

  • 데이터를 가져올 때는 한번에 가져오는 것이 아니라 작은 데이터를 가져와서 여러 번 만들어야한다.

  • 데이터를 가져올 때, 한번에 가져오면 서로 다른 것에서 영향을 받아서 코드가 매우 복잡해진다.

  • 그래서 각부분을 위해서 고립화가 필요하다!




3) 고립화로 Vue.js 코드를 만들 때, 장점 :

  • a. 코드의 단순화 : 코드 구조가 단순해진다.

  • b. 코드의 집중화 : 코드의 재사용과는 의미가 다르다. 동시대의 만들어지는 코드들이다. 한 번에 수정이 일어나야 하는 것들이다.

  • c. 협업이 가능하다 : 코드를 여러 사람들과 분업하여 나눠서 만들수 있다.

  • d. 코드의 재사용이 가능하다. : 다음 버전에서 사용하면 코드의 재사용성이 가능하다. 코드의 재사용은 다음 시대(다음 계층)에서 사용한다.

  • 결론** : Vue에서는 view단의 각각의 부분들이 서로 영향을 주면 안되는 고립화가 완전히 일어나야 한다.




4) 고립화해서 Vue.js 만드는 방법 : ‘컴포넌트’ 개념!

a. 기존 rland 프로젝트 변경

  • admin 폴더만 static 폴더로 옮긴다.


b. ‘컴포넌트’ 개념!

  • 이제는 서버에서 페이지를 만들 일이 없다.

  • 먼저, index.html 페이지에서 NodeJS 실행환경에서 npm으로 서버를 실행하여 브라우저에서 웹을 동작시킨다. 이제는 서버를 키고 더 이상 서버를 요청하지 않는다!!

  • 그래서, 모든 페이지를 SPA(Single Page Application) 프로젝트로 만들자!

  • 그래서 우리는 NodeJS를 써야 한다.




5) 이전의 SSR 정리

  • 우리는 이전까지 서버를 만들어주고 데이터를 만들어주는 ‘Front-Controller’를 만들었다.
    • JSP, Thymeleaf 등으로 출력을 위해서 view단을 이용했다.


  • 또한, 기존에 Thymeleaf를 통해 html만 가지고 요청하는 경우만 했었다.


  • 이제는 JS 코드로 서버를 요청한다. 하지만, 이렇게 하면 JS 코드로 화면을 조작하는 작업이 불가능했었다. 그래서 DOM을 이용했었는데 앞으로는 DOM을 직접 사용하지 않고 Vue MVC를 이용한다.


  • 앞으로는 Vue MVC에서 컴포넌트 개녕을 도입해서 Vue로 빌드된 것만 이용해서 Node에 도구를 얹혀서 웹 view단을 만들어가는 방법을 할 것이다. 코드가 편해진다.



6) Node.js 시작!!**

  • node.js는 JS 코드의 결과를 브라우저에서가 아니라 이제는 UI로서 결과를 확인할 수 있다.(콘솔창을 이용한 실행환경)


  • Read Eval Print Loop : Node.JS를 이렇게 부르고 1줄 단위로 코드를 읽어 들여서 실행한다, 렛풀 방식의 코드 실행


  • CLI : Node.JS를 ‘Command Line Interface(Interpreter)’라고도 부른다.


  • 추가로 Node.JS를 배치 형태로 실행할 수 없을까? :
    • NodeJS에서는 자바와 같아 보이지만 파일 입출력이나 네트워크(웹 개발을 위한 HTTP 모듈), 프로세서 관리에서만 사용할 수 있다.
    • 또한, NodeJS는 브라우저가 아니라서 DOM을 사용할 수 없다.


  • 메모장에 실습코드 :
    • first.js

let x=30;
let y=30;
let y= x+y;
console(z);


  • CLI에서 실행 명령어 :

node first




7) npm 사이트 이용하기.

  • npm 사이트에는 모듈을 언제든지 올릴 수 있다. 또한, 다른 개발자들에 의해 만들어진 도구인 라이브러리를 다운받을 수 있다.


  • npm 명령어로 모듈(라이브러리)을 가져올 수 있다.
    • 명령어 : npm i "다운받을 모듈명";


  • 하나의 JS 코드에서 다른 JS 코드를 사용할 수 없었다가 모듈을 통해 사용할 수 있게 되었다. 모듈은 ES5부터 있었다.


  • 기본은 CommonJS를 사용한다. 우리가 앞으로 사용할 것은 ES6에서 추가된 모듈을 사용한다.


a. 다운받은 기본 라이브러리 실행에 관한 실습 코드 :

  • JS에서는 ‘require()’라는 함수를 통해서 남이 만든 npm 라이브러리를 사용할 수 있다. require() 함수 사용한 것을 새로운 변수인 객체에 담아서 사용하는데 그 객체는 기존에 만들어진 라이브러리에서 exports를 통해 배포된 라이브러이다. exports는 공유된 라이브러리 객체의 내부 속성을 사용할 수 있게 해준다.

// modules 객체에 담아서 그 속성을 사용할 수 있다. 'module'이라는 이름은 예약어라서 바꾸자

const modules = require("newlec-hello");    
modules.hello();	// 해당 모듈의 속성을 이용할 수 있다.(해당 라이브러리 내부 객체의 함수 사용 가능)


  • 정리** : NodeJS는 단순히 실행환경이다. 그래서, 앞으로는 Vue를 이용하는 플랫폼을 이용할 예정이다. 다양한 도구를 NodeJS에 다운받아서 사용할 수 있다.(npm 사이트 이용)


b. npm 사이트 제대로 이용하기 :

  • 우리는 npm 사이트에서 ‘라이브러리’를 받는지 ‘툴’을 받는 건지 알고 있어야 한다.


  • 라이브러리를 다운받으면 그 파일에서 package.json 파일을 확인해보자. 이 파일이 스프링부트의 pom.xml 파일과 같은 역할을 하는 ‘라이브러리 버전 관리 파일’이다.


  • 해당 라이브러리 파일의 폴더를 지우고나서 package.json이 있다면, npm i 명령어를 통해서 다시 다운 받을 수 있다. package.json 파일에서 그 라이브러리 내용을 지우고 저장해도 그 모듈이 워크스페이스에서 저절로 사라진다.


c. 리눅스 환경의 장점 :

  • 리눅스에서는 개발용에서 모든 프로그램이 개발이 가능하다. 해킹이나 모니터링을 체킹할 수 있는 프로그램도 해당 개발환경에서 실행할 수 있다.


  • 프로젝트를 서버에서 돌리기 위해서는 리눅스에서 사용해야 한다. 왜냐하면, 윈도우즈는 리소스가 차기 때문에 1년에 한번 없애줘야 한다. 또한, 윈도우즈 업데이트 때문에 서버를 돌릴 수가 없다.


  • 로드 밸런싱, 클러스터링 등 기능이 전부 리눅스에 있다. 그래서, 배포 운영에는 리눅스를 사용한다. 서버를 돌리기 위해서 다른 OS에서는 에뮬레이터로 개발 환경 관련 프로그램을 써야한다.


  • 그래서, 앞으로 다양한 개발 환경을 위해 리눅스도 공부하자!



8) Vue를 npm 이용하기!!

  • 유닉스 명령어
    • ‘pwd’ : 현재 폴더 경로
    • clear : 현재 터미널 내부의 명령어 지우기


  • npm init vue@latest와 같은 해당 명령어를 이용하면 다음과 같은 파일을 다운 받을 건지 물어본다.
    • 타입스크립트 : 실행어를 자바스크립트로 바꿔준다. 나중에 사용하자. 쓸만 한듯?
    • JSX : 리액트에서 쓰는 뷰단 엔진, 엉망이라서 버리자!
    • vite : 개발용 서버 도구이다. 라이브서버 같은 느낌이다.


a) 실행 명령어

  • npm init vue@latest : npm에서 Vue를 라이브러리로 다운 받을 수 있다. 모듈로 사용할 수 있다.
  • npm i : pom.xml 파일과 같은 파일인 package.json 파일을 실행해서 해당 라이브러리를 업데이트 시키자
  • npm run dev : node 서버 실행


  • package.json 파일에서 “scripts”라는 객체를 만들어서 속성으로 starts(npm 예악어에 해당하는 starts)로 만들면 npm 명령어로 실행할 수 있다.
  • 내가 만든 객체로 실행하기 위해서 ‘npm run “라이브러리 이름”‘으로 서버를 실행해서 포트번호 ‘5173’를 이용한다!


  • 이제는 컴포넌트가 교체되는 방식(라우팅? 방식)이라서 페이지가 다시 로딩이 되는 것이 없다. src 폴더에서 components 폴더를 만들어서 프로젝트 디렉토리 구조로 사용한다.



9) 정리**

  • npm은 ‘mvn repository’과 같은 앱스토어이며 JS 코드가 NodeJS 실행 환경에서 동작할 수 있도록 서버를 켜주는 것과 같다. 여러가지 도구를 붙여서 사용할 수 있다.
  • NodeJS는 jdk와 같다. 실행 환경 그 자체이다.
  • NodeJS는 단순히 실행환경이다. 그래서, 앞으로는 Vue를 이용하는 플랫폼을 이용할 예정이다. 다양한 도구를 NodeJS에 다운받아서 사용할 수 있다.(npm 사이트 이용)



2. Vue Component : 230404

1) .vue 파일이 동작하는 과정(배경 설명)

  • Vue를 동작시키기 위해서는 실행시킨 사람에게 권한을 부여해서 그 사람에게만 동작하게 하기 위해 그냥 로컬에서 서버 없이 파일을 브라우저에서 실행시키면 안 된다.
  • 브라우저의 권한으로 Vue가 실행되는 것이 아니라 로컬에서 Vue 엔진이 있는 서버를 실행시킨 사람에 의해서 Vue를 실행시킨다.
  • Vue 엔진은 ECMA script가 표준 JS라서 이것을 기준으로 동작하게 하자!


  • dev 서버 : npm이 관리하는 서버가 dev 서버가 있다.


a. NodeJS 개발의 변화 과정


  • webpack?(웹팩) : webpack은 entry, output의 역할을 갖는 변환기가 있다.


  • babeljs?(바벨JS) : 상위 버전에서 하위 버전으로 바꿔주는 변환기이다.(ES6 -> ES5 버전)


  • bite = webpack + dev 서버 = bite는 보완된 개발용 서버
    • 번들링도 쉽고 서버도 따로 구현할 필요가 없다!


  • 왜 사용하지 ? : 복잡한 구조로 되었있는 JS와 HTML 그리고 CSS 파일을 변환기로 알아서 사용하기 쉽게 바꿔주므로 편하다!


  • css 프레임워크 : sass, less(프로그래밍 언어 같은 느낌)
    • sass에서는 -- 가 변수이다.
    • sass는 css에서도 조건문이 가능해서 계산기 같은 것에서 사용한다. 변환해서 사용한다.


  • 우리는 지금껏 vite를 사용한 것이고 vue는 단순히 라이브러리였다. vite에다가 vue를 가져다가 사용한 것이다.


  • .vue 파일을 사용하는 이유는 변환기를 쓰겠다는 이유이다. JS의 문법이 안 맞아도 변환기를 통해서 JS 기본 문법도 알아서 바꿔준다.(ES6문법에서도 ES5 문법을 이용할 수 있다.)


b. Single File Component :

  • 이전까지는 Single 파일에다가 3가지 파일(html, css, js)을 섞어서 사용할 수 없었는데 이제는 가능하다.
  • script 태그, template 태그, style 태그 모두를 이용할 수 있다.
  • 서로 다른 코드가 영향을 안주고 고립화가 가능하다.


  • bite의 역할: 원래 JS 파일에서는 JS 코드 이외에는 사용할 수 없었는데 이 3가지 모든 코드를 읽어서 같이 쓸 수 있게 알아서 번들링해준다.


  • dev 명령어 : 번들링하고 웹서버까지 켜준다.
  • build 명령어 : 번들링까지만 해준다.(.vue 파일들을 dist 폴더에 html,css,js로 번들링해서 각각 만들어준다.)



2) Vue 컴포넌트 모듈 이용

  • 컴포넌트 태그로 만들어서 또 다른 파일에 template 태그에 꽂아 넣을 수 있다.
  • .vue 파일을 아직 이용하지 않고 JS 파일로만 컴포넌트 개념 이용


a) 실습코드


  • calc.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Insert title here</title>
	<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
	<script type="module" src="./app.js" defer="defer"></script>
</head>

<body>
	<div id="app"></div>


</body>
</html>


  • app.js
import Calc from './calc2.js';

// import로 불러와서 createApp에 넣어주면 꽂아서 사용할 수 있다.
// Vue
// .createApp(Calc)    // {} 대신해서 모듈을 넣을 수 있다!
// .mount("#app");

// app.js가 application이고 calc2.js가 object이다.

// 어플리케이션 동작하는 곳을 따로 두었다.
// 계산기는 어플리케이션의 일부에 해당하기 때문이다.


Vue
.createApp({
    template:`<section>
        <h1>다양한 도구</h1>
        <Calc/>
        <Calc/>
        <Calc/>
    </section>`
})    // {} 대신해서 모듈을 넣을 수 있다!!
.mount("#app");


  • calc.js

export default {
	data(){		// 데이터를 반환하는 것이 모델을 반환한다.
			let x = 0;
			let y = 0;
			let z = 0;
				
			return {x,y,z};	
	},
	// 객체 안에 함수를 정의한다.
	methods: {
		calcHandler(e){
			this.z = this.x+this.y;
		
		},
		resetHandler(e){
			this.z = 0;
			
		}		
	},
	template:`<section id="calc">
	    <h1>덧셈 계산기</h1>
	    <form>
	        <fieldset>
				<!-- 는 innerTxt에서 사용한다. -->
	            <legend>계산기 입력폼</legend>
	            <div>
	                <label>x:</label>

					<input dir="rtl" name="x" v-model.trim.number="x" @input="this.z-this.x;">
	                <label>y:</label>
	                <input dir="rtl" name="y" v-model.number="y">
	                <span>=</span>
	                <span v-text="z"></span>
	            </div>
	            <hr>
	            <div>	
					<input type="submit" value="초기화" @click.prevent="resetHandler">
	                <input type="submit" value="계산하기" @click.prevent="calcHandler">
	            </div>
	        </fieldset>
	    </form>
	</section>`
	
}





3) Vue 컴포넌트 태그 이용


  • 고립화된 하나의 파일에서
    • 다른 파일과 영향을 받지 않는다.
<template>
    <header class="header fixed-header">
        <div>
            <h1 class="header-title"><a href="/index.html"><img class="logo" src="../image/logo-w.png" alt="알랜드"></a></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><a class="" href="/menu/list.html">카페메뉴</a></li>
                <li><a class="" href="/notice/list.html">공지사항</a></li>
                <li><a class="" href="/user/login.html">로그인</a></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">부안에 오면 ! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
</template>

<script>

</script>

<style>

</style>




4) Vue에서 제공해주는 Vue 엔진 이용하기!!

  • 이게 최종 목적지이며 제일 편하다!


a. 실습 코드 :

  • index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="/css/reset.css" type="text/css" rel="stylesheet" >
    <link href="/css/style.css" type="text/css" rel="stylesheet" >
    <link href="/css/layout.css" type="text/css" rel="stylesheet" >
    <link href="/css/header.css" type="text/css" rel="stylesheet" >
    <link href="/css/footer.css" type="text/css" rel="stylesheet" >
    
    <link href="/css/user/find-id.css" type="text/css" rel="stylesheet" >
    <link href="css/buttons.css" type="text/css" rel="stylesheet" >
    <link href="css/icon.css" type="text/css" rel="stylesheet" >
    <link href="css/deco.css" type="text/css" rel="stylesheet" >
    <link href="css/utils.css" type="text/css" rel="stylesheet" >    
</head>

<body>    
    <div id="app"></div>
    <script type="module" src="src/main.js"></script>

</body>

</html>


  • main.js
Vue
.createApp(App)
.mount("#app");

// .Vue파일은 Vue가 제공해주는 변환 엔진기이다.



  • App.vue
<template>
    <header class="header fixed-header">
        <div>
            <h1 class="header-title"><a href="/index.html"><img class="logo" src="../image/logo-w.png" alt="알랜드"></a></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><a class="" href="/menu/list.html">카페메뉴</a></li>
                <li><a class="" href="/notice/list.html">공지사항</a></li>
                <li><a class="" href="/user/login.html">로그인</a></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">부안에 오면 꼭! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 길</a></div> 
        </div>
    </nav>

    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄 수 있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="./image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 맛, 과테말라
                    산의 풍미, 그 외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="./image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
                <div class="d-flex justify-content-between">
                    <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
                    <div class="d-flex align-items-center">
                        <a class="btn btn-leaf mr-3" href="">전체명소</a>
                    </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                서 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 듯..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="./image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="./image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="./image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="./image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="./image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="./image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
        </section>

        <!-- ---------------------------------------------------------------------- -->
        <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>

        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>
    
    <footer class="footer">
        <h2>알랜드(Rland)</h2>
        <div>
            copyright @ rland.co.kr 2022-2022 All Right Reservved. Contact admin@rland.co.kr for more information
        </div>
    </footer>
</template>



5) JS importmap 개념

  • 보통, JS의 importmap은 원격에 있는 script 파일을 import할 떄, 사용한다.

<script type="importmap">
 	{
 		"imports" :{
 			'vue':'http://vue.js.org/module/aa.js'
 		}
 	}
</script> 



  • 이 개념을 가져와서 Vue에서 사용한다.
  • 하지만 vue 라이브러리는 https://cdnjs.com/에서 올라가 있어서 예약어로 설정되어 가져와서 사용한다.

import App from './App.vue';
import {createApp} from 'vue'   
// 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.

// createApp는 MVC 프레임워크에서 컨트롤러를 만들어준다. 
createApp(App)	
.mount("#app");



a. Vue 라이브러리 개념 정리


  • 기존의 global options API는 Vue 변환기가 없을때 사용했었다. 함수나 객체를 끌어와서 사용할 때 사용!
  • vue.global.min // vue.global.prod.min.js : 이전에 사용했던 방식이다. 글로벌 객체를 가져와서 사용했었다.


  • vue.esm-bundler.min.js 파일 : 번들용 파일이다. 이 라이브러리는 글로벌 객체를 제공하지 않고 모듈을 제공해준다!!
    • 우리는 모듈화된 Vue 라이브러리를 사용해야 한다!!


  • cdn(콘텐츠 전송 네트워크) : 구글이 제공하고 있는 JS 라이브러리들 일일이 홈페이지가서 다운을 받아야하는데 그러면 같은 파일이 여러개가 생긴다! 그것에 대한 파일이 겹치지 않고 편하게 다운받게 해준다.
    • 하지만, 원격 저장소인 cdn에서 파일이 깨질수가 있어서 이러한 사태를 막기 위해서 js 폴더에도 그 파일도 다운받아놔야 한다.



6) App.vue 파일 가져오기

  • index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="/css/reset.css" type="text/css" rel="stylesheet" >
    <link href="/css/style.css" type="text/css" rel="stylesheet" >
    <link href="/css/layout.css" type="text/css" rel="stylesheet" >
    <link href="/css/header.css" type="text/css" rel="stylesheet" >
    <link href="/css/footer.css" type="text/css" rel="stylesheet" >
    
    <link href="/css/user/find-id.css" type="text/css" rel="stylesheet" >
    <link href="css/buttons.css" type="text/css" rel="stylesheet" >
    <link href="css/icon.css" type="text/css" rel="stylesheet" >
    <link href="css/deco.css" type="text/css" rel="stylesheet" >
    <link href="css/utils.css" type="text/css" rel="stylesheet" >    
</head>

<body>    
    <div id="app"></div>
    <script type="module" src="src/main.js"></script>

</body>

</html>


  • main.js
import App from './App.vue';
import {createApp} from 'vue'   
// 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.

createApp(App)
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()


  • App.vue
<template>
    <header class="header fixed-header">
        <div>
            <h1 class="header-title"><a href="/index.html"><img class="logo" src="../image/logo-w.png" alt="알랜드"></a></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><a class="" href="/menu/list.html">카페메뉴</a></li>
                <li><a class="" href="/notice/list.html">공지사항</a></li>
                <li><a class="" href="/user/login.html">로그인</a></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">부안에 오면 꼭! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="./image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 길</a></div> 
        </div>
    </nav>

    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄 수 있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="./image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="./image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 맛, 과테말라
                    산의 풍미, 그 외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="./image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
                <div class="d-flex justify-content-between">
                    <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
                    <div class="d-flex align-items-center">
                        <a class="btn btn-leaf mr-3" href="">전체명소</a>
                    </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                서 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 듯..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="./image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="./image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="./image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="./image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="./image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="./image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
        </section>

        <!-- ---------------------------------------------------------------------- -->
        <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>

        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>
    
    <footer class="footer">
        <h2>알랜드(Rland)</h2>
        <div>
            copyright @ rland.co.kr 2022-2022 All Right Reservved. Contact admin@rland.co.kr for more information
        </div>
    </footer>
</template>



7) 배포본 만들기

  • 브라우저에서 404 에러가 나는 것을 모두 고치고 빌드해서 배포본을 만들어야 한다.

  • js, css 파일을 모두 절대 경로로 바꿔야지 에러가 안 난다.

  • 배포본 만드는 명령어 : npm run build

  • 이렇게 배포본이 만들어지면 dist 폴더에 생기는데 이 폴더를 통으로 가져가서 springboot 프로젝트에서 static 폴더에서 사용한다.


  • 매번 vue 파일을 빌드해서 springboot로 가져가거나 서로 같은 디렉토리에서 config 파일 설정을 통해 연동을 시키거나 2가지 방식중 하나를 선택하자**
    • Vue와 SpringBoot 연동을 하는 경우 프록시 서버를 이용하기 때문에 CORS 문제 해결을 해야한다.
    • Vue와 SpringBoot 연동을 안 하는 경우, 프론트 작업의 코드가 변경되면 매번 빌드해서 가져가야하는 번거로움이 생긴다.





8) .vue 파일에 모델 심고 컴포넌트 나누기

  • 아래 코드처럼 컴포넌트 개념으로 나눈 것은 이전에 했던 tiles와 방식이 같다.


a. 실습 코드 :

  • index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="/css/reset.css" type="text/css" rel="stylesheet" >
    <link href="/css/style.css" type="text/css" rel="stylesheet" >
    <link href="/css/layout.css" type="text/css" rel="stylesheet" >
    <link href="/css/header.css" type="text/css" rel="stylesheet" >
    <link href="/css/footer.css" type="text/css" rel="stylesheet" >
    
    <link href="/css/user/find-id.css" type="text/css" rel="stylesheet" >
    <link href="css/buttons.css" type="text/css" rel="stylesheet" >
    <link href="css/icon.css" type="text/css" rel="stylesheet" >
    <link href="css/deco.css" type="text/css" rel="stylesheet" >
    <link href="css/utils.css" type="text/css" rel="stylesheet" >    
</head>

<body>    
    <div id="app"></div>
    <script type="module" src="src/main.js"></script>

</body>

</html>


  • main.js

import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.

createApp(App)
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()


  • App.vue
<!-- script 태그는 예전에 썼던 방식으로 사용하면 된다! -->
<!-- export default {} 가 필요하다! -->

<script>
    import Header from './components/Header.vue';
    import Footer from './components/Footer.vue';

    export default {
        components:{
            Header,
            Footer
        },
        data() {
            return {
                test:"하이"
            }
        }
    }
</script>

<!-- script setup 태그는 쉽다! -->
<script setup>

</script>
<template>
    <Header/>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">부안에 오면 꼭! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 길</a></div> 
        </div>
    </nav>

    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄 수 있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="/image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 맛, 과테말라
                    산의 풍미, 그 외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="/image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
                <div class="d-flex justify-content-between">
                    <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
                    <div class="d-flex align-items-center">
                        <a class="btn btn-leaf mr-3" href="">전체명소</a>
                    </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                서 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 듯..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="/image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="/image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="/image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="/image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="/image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="/image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
        </section>

        <!-- ---------------------------------------------------------------------- -->
        <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>

        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>
    
    <Footer/>
</template>


  • Header.vue
<template>
    <header class="header fixed-header">
        <div>
            <h1 class="header-title"><a href="/index.html"><img class="logo" src="/image/logo-w.png" alt="알랜드"></a></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><a class="" href="/menu/list.html">카페메뉴</a></li>
                <li><a class="" href="/notice/list.html">공지사항</a></li>
                <li><a class="" href="/user/login.html">로그인</a></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
</template>


  • Footer.vue
<template>
    <footer class="footer">
        <h2>알랜드(Rland)</h2>
        <div>
            copyright @ rland.co.kr 2022-2022 All Right Reservved. Contact admin@rland.co.kr for more information
        </div>
    </footer>
</template>




3. Vue Router : 230405


1) Router 개념**


  • Vue Router 설치 방법 : npm install vue-router@4


  • yarn vs npm : gradle vs maven 같은 느낌


a. Vue Router 사용 방법**

  • 보통 router-link 태그와 router-view 태그 2가지만 주로 이용한다.**


  • 페이지 이동 방법** :
    • router-link 태그를 이용!(url link를 이용한다.)
    • 그 공간을 빼서 다른 공간을 꽂아 넣을 수 있다.


  • 페이지 구성 방법(페이지 집중화)** :
    • Router view
    • 관리자 페이지와 사용자 페이지가 완전 다를 때, Root 위치에 Router view만 있는 상태에서 다 바꿔치기를 해야 한다.


  • 주의 할 사항** :
      • 위의 두개의 태그를 다 사용 가능하다. ‘-‘를 쓰던 말던 같게 사용한다.


b. 실습 코드 설계 방법

  • 관리자 레이아웃과 사용자용 레이아웃이 다르다는 것을 인지하고 설계하자!**


  • App.vue
<!-- script 태그는 예전에 썼던 방식으로 사용하면 된다! -->
<!-- export default {} 가 필요하다! -->

<script>
    export default {
        data() {
            return {
                x:21,
                y:23,
                test:"하이"
            }
        }
    }
</script>

<template>
    <router-view></router-view>
    <!-- <RouterView></RouterView> -->
</template>



  • 그냥 Layout.vue
<script>
    import Header from './Header.vue';
    import Footer from './Footer.vue';

    export default {
        components:{
            Header,
            Footer
        },
        data() {
            return {
                x:21,
                y:23,
                test:"하이"
            }
        }
    }
</script>

<template>
    <Header/>
    <!-- ---------------------------------------------------------------------- -->   
    <nav class="cafe">
        <div>
            <h1 class="cafe-title">부안에 오면 꼭! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 길</a></div> 
        </div>
    </nav>

    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄 수 있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="/image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 맛, 과테말라
                    산의 풍미, 그 외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="/image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
                <div class="d-flex justify-content-between">
                    <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
                    <div class="d-flex align-items-center">
                        <a class="btn btn-leaf mr-3" href="">전체명소</a>
                    </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                서 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 듯..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="/image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="/image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="/image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="/image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="/image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="/image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
        </section>

        <!-- ---------------------------------------------------------------------- -->
        <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>

        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>
    
    <Footer/>
    
</template>


  • 관리자용 페이지 Layout.vue
<template>
    관리자 페이지
</template>


  • main.js

import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.

import Layout from './components/Layout.vue';
import AdminLayout from './components/Admin/Layout.vue';


// 2. 여기서 Router를 매핑해준다! 
const routes = [
    { path: '/index', component: Layout },
    { path: '/admin/index', component: AdminLayout },
]
  
// 1. 여기서 Router를 생성해서 
const router = VueRouter.createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    // router 기록!
    history: VueRouter.createWebHashHistory(),
    routes, // short for `routes: routes`
})


// Vue.createApp()에서 변경!!
createApp(App)
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()




2) 모듈에서 쓰는 Router

  • import {createRouter, createWebHashHistory} from 'vue-router';
  • 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다.
  • 그래서 VueRouter.createRouter 이렇게 안쓰고 createRouter로 바로 사용한다!
import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.
import {createRouter, createWebHashHistory} from 'vue-router';    
// 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다. 


import Layout from './components/Layout.vue';
import AdminLayout from './components/Admin/Layout.vue';


// 2. 여기서 Router를 매핑해준다! 
const routes = [
    { path: '/index', component: Layout },
    { path: '/admin/index', component: AdminLayout },
]
  
// 1. 여기서 Router를 생성해서 
const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    // router 기록!
    history: createWebHashHistory(),
    routes // short for `routes: routes`
})


// Vue.createApp()에서 변경!!
createApp(App)
.use(router)    // App.vue 파일 내부의 router-view 태그에서 Layout과 Admin의 Layout이 교체된다.
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()



3) 내부의 컴포넌트(children) 바꾸기

  • 이거만 보더라도 router-view가 몇 개인지 알 수 있다.**
    • 템플릿이 바뀌는 행위 숫자 = router-view의 개수


  • children이 내부의 컴포넌트를 교체할 수 있게 해준다.
    • children은 JSON 형식으로 사용한다!([{key1 : value1},{key2 : value2}])


  • 컴포넌트 방식이 아니라 이렇게 템플릿을 간단히 쓸 수도 있다.(예전 방식)


a. 실습 코드 :

  • main.js
import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.
import {createRouter, createWebHashHistory} from 'vue-router';    
// 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다. 


import Layout from './components/Layout.vue';
import Index from './components/Index.vue';
// import Index from './components/Index.vue';

import AdminLayout from './components/Admin/Layout.vue';


// 2. 여기서 Router를 매핑해준다! 
// **이거만 보더라도 router-view가 몇개인지 알 수 있다.
// (템플릿이 바뀌는 행위 숫자 = router-view 숫자 )
const routes = [
    { path: '/', component: Layout, children:[
        { path: 'index', component:Index},
                // 컴포넌트 방식이 아니라 이렇게 템플릿을 간단히 쓸 수도 있다.(예전 방식)
        { path: 'about', component:{template:`어바웃!어바웃!어바웃!어바웃!어바웃!어바웃!어바웃!어바웃!`}}
    ] },
    { path: '/admin/index', component: AdminLayout },
]
  
// 1. 여기서 Router를 생성해서 
const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    // router 기록!
    history: createWebHashHistory(),
    routes // short for `routes: routes`
})


// Vue.createApp()에서 변경!!
createApp(App)
.use(router)    // App.vue 파일 내부의 router-view 태그에서 Layout과 Admin의 Layout이 교체된다.
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()



4) list, detail 페이지 구현

  • list 페이지와 detail 페이지를 구성할 때, children으로 더 레이아웃 계층이 내려가는 것이 아니라 해당 레이아웃을 공유하는 개념이라서 children을 사용하지 않는다.

  • 그래서, children을 사용하지 않고 { path: 'menu/list', component:MenuList},처럼 /를 사용한다.

    • import MenuList from './components/menu/List.vue';


a. 실습 코드

  • main.js
import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.
import {createRouter, createWebHashHistory} from 'vue-router';    
// 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다. 


import Layout from './components/Layout.vue';
import Index from './components/Index.vue';
import About from './components/About.vue';
import MenuList from './components/menu/List.vue';
import MenuDetail from './components/menu/Detail.vue';

import AdminLayout from './components/Admin/Layout.vue';




// 2. 여기서 Router를 매핑해준다! 
// **이거만 보더라도 router-view가 몇개인지 알 수 있다.
// (템플릿이 바뀌는 행위 숫자 = router-view 숫자 )
const routes = [
    { path: '/', component: Layout, children:[
        { path: 'index', component:Index},

        // children으로 더 내려가는 것이 아니라 해당 레이아웃을 공유하는 것이다. 
        { path: 'menu/list', component:MenuList},
        { path: 'menu/detail', component:MenuDetail},

                // 컴포넌트 방식이 아니라 이렇게 템플릿을 간단히 쓸 수도 있다.(예전 방식)
        { path: 'about', component:About}
    ] },
    { path: '/admin/index', component: AdminLayout },
]
  
// 1. 여기서 Router를 생성해서 
const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    // router 기록!
    history: createWebHashHistory(),
    routes // short for `routes: routes`
})


// Vue.createApp()에서 변경!!
createApp(App)
.use(router)    // App.vue 파일 내부의 router-view 태그에서 Layout과 Admin의 Layout이 교체된다.
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()



  • Vue로 프로그래밍을 하면, 소스코드에서 코드가 안보여서 문서를 공개할 수 없다. 문서를 공개해야하는 경우는 불리하지만 앱(어플리케이션)을 만들 때는 괜찮다.

  • 또한, 페이지 이동 시, router-linkto를 이용한다.

  • router-linka 태그, tohref의 역할을 해준다.

<template>
    <header class="header fixed-header">
        <div>
            <h1 class="header-title"><router-link to="/index"><img class="logo" src="/image/logo-w.png" alt="알랜드"></router-link></h1>
            
            <ul class="main-menu d-none d-inline-flex-sm">
                <li><router-link class="" to="/menu/list">카페메뉴</router-link></li>
                <li><a class="" href="/notice/list">공지사항</a></li>
                <li><router-link class="" to="/admin/index">로그인</router-link></li>
            </ul>
            <div class="d-none-sm"><a class="icon icon-menu icon-white" href="?m=on">메뉴버튼</a></div>
        </div>
    </header>
</template>



6) Vue lifecycle 정리*

  • createApp는 MVC를 지원하는 라이브러리이다. 이게 컨트롤러를 만들어주는 역할을 해준다.
  • JS로 만든 모델을 vue의 mount에 라이브러리로 입혀준다.
  • Vue에서 지원해주는 프론트 작업이 가능한 MVC 프레임워크이다.

createApp(App)
.use(router)    // App.vue 파일 내부의 router-view 태그에서 Layout과 Admin의 Layout이 교체된다.
.mount("#app");




4. Vue Project : 230406

1) createApp 의미 :

  • createApp은 지시 사항을 전달만 해준다.(지시자 역할)
    • 예전에 스프링의 리졸브를 깨워주기 위해서 front-controller를 실행했었던 것과 같은 역할을 해준다.
  • 아래 코드에서 createApp이 컨트롤러를 만들어주고 App이 컨트롤러 객체이다.**

  • 아래 예시의 2가지 방법으로 Vue 엔진을 이용할 수 있다.
    • 간단히 지시자를 넘겨주는 것이다. 생략 가능
  • App이 컨트롤러 객체이고 App.vue 파일 내부 코드에서 router-view에 여러 개의 Layout.vue가 꽂힐 수 있다. 그래서, 우리는 Layout.vue에 다른 .vue 파일을 바로 꽂아서 쓸 수 없다. 왜냐하면, Layout.vue가 Admin이나 Member 그리고 Root 경로와 같이 여러 군데 존재할 수 있기 때문이다.(인터페이스 개념)

let app = creatApp(App);
app.use(router);
app.mount("#app");


creatApp(App);
use(router);
mount("#app");




2) .vue 파일에 css 작업


a. 현재 상황 :

  • 1) vue 파일의 template 태그 상단에 style 태그를 만들어서 @import url(/css/경로);로 스타일링을 입히자!

  • 2) 현재 상황 : index 페이지만 Header의 스타일링만 달라서 레이아웃을 따로 만들어야 한다!(Header가 position이 fixed 되는 경우)

  • 3) 변경 상황 : 이제는 index 페이지에 Header 태그를 갖고 있어서 css를 임의로 수정해도 된다.

  • 4) index 페이지에서만 헤더 스타일링 모습이 달라야 한다. 그래서, index 페이지에 추가로 스타일링하고 레이아웃에서도 뺐는데 List 페이지와 같이 다른 페이지에도 CSS 스타일링 영향을 주었다.


b. 문제점** :

  • 원인 : 서로 .vue 파일의 router 경로가 절대 경로인 ‘/’가 겹처서 사용되어서 충돌되었다. 그래서, 해결 방법으로 Index.vue 파일을 router 레이아웃에서 뺐는데 Index.vue와 Admin에서 List.vue의 헤더 스타일링이 여전히 같게 동작했다.

  • 해결 방법** : style 태그에 scoped를 사용해서 고립화 시키기!!**


c. 해결 방법** :

  • 단순히 css의 속성의 이름이 같다면, 상관이 없다. style 태그에 scoped를 적어주면 고립화가 되어 각자 동작하여 css 스타일링이 달라진다.

  • 하지만, css를 설계할 때, css 태그의 이름을 스타일링이 같은 것으로 집중화 안 시키고 만들면 이것은 문제가 된다. vue를 이용한 컴포넌트 설계에서 구조가 다 망가진다.


d. 실습 코드 :

  • Index.vue
<script>
    import Header from './Header.vue';
    import Footer from './Footer.vue';

    export default{
        components :{Header, Footer}
    }
</script>

<style scoped>
/* index페이지만 Header의 스타일링만 달라서 레이아웃을 따로 만들어야 한다! */
/* 이제는 index 페이지에 Header를 갖고 있어서 수정해도 된다! */
/* index 페이지에서만 헤더가 달라야 한다. */

    .header{
        position:fixed;
    }
</style>

<template>
        <Header />
        <nav class="cafe">
        <div>
            <h1 class="cafe-title">thymeleaf 부안에 오면 ! 들리는 카페</h1>
            <div class="mt-auto d-flex justify-content-end"><a class="btn btn-leaf mr-3" href="menu/list.html">전체메뉴</a></div>
            <nav class="top-menu item-list">
                <h1 class="d-none">인기메뉴</h1>
                <ul class="d-inline-block">
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/17.png" alt="딸기청">
                        <a target="_blank" href="menu/detail.html">딸기청</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/8.png" alt="카페 아메리카노">
                        <a target="_blank" href="menu/detail.html">카페 아메리카노</a>
                    </li>
                    <li>
                        <img height="100" src="/image/product/28.png" alt="버터쿠키">
                        <a target="_blank" href="menu/detail.html">버터쿠키</a>
                    </li>
                </ul>
            </nav>
            
            <div><a class="btn btn-block-link deco deco-location deco-white deco-mr-1 deco-ml-1" href="">알랜드 오시는 </a></div> 
        </div>
    </nav>
    
    <!-- ---------------------------------------------------------------------- -->
    <section class="rland">
        <div>
            <h1 class="d-none">알랜드 소개</h1>
            
            <article class="">
                <h1 class="font-family-arial">직접 만든 과일청을 맛보세요.</h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 과일과 알랜드만의 레시피로
                    과일향의 풍미를 충분히 느낄  있는
                    수제청을 드셔보세요.
                </p>
            </article>
            

            <article>

                <h1>
                    직접 구운 수제 쿠키를 만나보세요.
                </h1>
                <div><img src="/image/product/17.png" alt="과일청"></div>

                <p>
                    신선한 버터 그리고 견과류를
                    이용해 바삭함을 더해 매일마다
                    직접 구운 맛난 쿠키를 만나보세요.
                </p>
            </article>

            <article>
                <h1>
                    다양한 로스팅으로 다채로운 맛을 느껴보세요.
                </h1>
                <div><img src="/image/product/18.png" alt="과일청"></div>

                    
                <p>
                    코롬비아산의 상큼한 , 과테말라
                    산의 풍미,  외에 5가지 이상의
                    다채로운 원두의 맛을 느껴보세요.
                </p>
            </article>

            <article>
                <h1>
                    알랜드 주변의 명소를 찾아보세요.
                </h1>
                <div><img src="/image/product/19.jpg" alt="과일청"></div>


                <p>
                    알랜드 주변에는 30곳이 넘는
                    힐링 장소에서 맛나는 커피와 경치로
                    힐링을 해보세요.
                </p>      
            </article>
        </div>
    </section>
    <!-- ---------------------------------------------------------------------- -->
    <section class="spot">
        <div>
            <header class="spot-header">
	            <div class="d-flex justify-content-between">
	                <h1 class="spot-title deco deco-location deco-white deco-mr-1 ml-3">부안의 아름다운 명소</h1>
	                <div class="d-flex align-items-center">
	                	<a class="btn btn-leaf mr-3" href="">전체명소</a>
	                </div>
                </div>
            </header>
            <div>
                <article>
                    <header>
                        <h1>채석강</h1>
                        <div lass="deco-pen"><a href="">89</a></div>
                    </header>
                    <div>
                        <p>
                            <a href="">
                                바닷물에 침식이 되어
                                 쌓인 절벽이 여러
                                권의 책을 쌓아 놓은 ..
                            </a>
                        </p>
                        <div>
                            <a href="https://map.naver.com/v5/search/%EC%95%8C%EB%9E%9C%EB%93%9C/place/1281711613?placePath=%3Fentry=pll%26from=nx%26fromNxList=true&c=14084751.7063168,4258510.9452342,13.3,0,0,0,dh" target="_blank">
                                <img src="/image/spot/map.png" alt="">
                            </a>
                        </div>
                    </div>
                </article>
                <nav class="item-list">
                    <h1 class="d-none">명소목록</h1>
                    <ul>
                        <li><img src="/image/spot/b2.jpg" alt="바위"><a href="">바위</a></li>
                        <li><img src="/image/spot/b13.jpg" alt="명소2"><a href="">명소2</a></li>
                        <li><img src="/image/spot/b14.jpg" alt="명소3"><a href="">명소3</a></li>
                        <li><img src="/image/spot/b7.jpg" alt="명소4"><a href="">명소4</a></li>
                        <li><img src="/image/spot/b9.jpg" alt="명소5"><a href="">명소5</a></li>
                    </ul>
                </nav>
            </div>
        </div>
    </section>

    <!-- ---------------------------------------------------------------------- -->
    <section class="visited-review">
        <!-- <header>
            <h1>방문 네이버 리뷰</h1>
            <div>더보기</div>
            <div>영수증으로 인증된 이용자가 네이버를 통해 남긴 평가입니다.</div>
        </header>

        <section>
            <h1>리뷰 목록</h1>
            <div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>

                </div>
                <div>
                    <div>5</div>
                    <p>탁트인 창이 곳곳에 있어 좋아용 음료도 맛나요 사장
                        님이 만드신 수제과자도 너무 고소하니...</p>
                    <div>bhj**** </div>
                    <div>2021.09.15</div>
                    <div>1번째 방문</div>
                </div>
            </div>
        </section>
		
        <div>
            <a href="">이전</a>
            <a href="">다음</a>
        </div> -->

    </section>

    <section class="blog">
    </section>
    <section class="location"></section>
    <section class="business-hour"></section>

    <Footer/>
</template>




3) Vue와 SpringBoot 연동

a. CORS 문제

  • CORS 설정 스프링부트 레퍼런스

  • 보통, 브라우저에서 클라이언트는 문서(JS)를 받아서 그 문서(JS)가 다시 서버에서 데이터를 요청한다.

  • 문서를 받은 요청(클라이언트쪽에 요청, Vue 엔진)과 데이터 요청 서버(서버쪽에 요청, AJAX와 RESTAPI의 통신에서 SpringBoot의 톰캣 서버)가 같으면 origin이 같다고 한다.

  • 이러한 origin이 다른 경우를 서버가 다르다고 한다. 이것을 ‘Cross-Origin’이라고 한다.

  • 하지만, 예전에는 CORS를 허용했는데 지금은 막았다. 그 이유는 무임승차로 origin 서버를 이용하는 것 같아서 막았다.

  • 같은 데이터를 다른 곳에서 데이터를 가져가는 것을 막았다. 예를 들어, 네이버 데이터 정보를 네이버에서 제공 해주는 것이 아니라 다른 사이트에서 제공해주는 것을 막아 준 것과 같다.



b. 예시 코드 (AJAX)

<script>
export default {
    data() {
        return {
            list:[]
        };
    },
    mounted(){
        fetch("http://localhost:8080/")
        .then(response=>response.json())
        .then(list=>{this.list=list})
    },

    methods: {
        
    },
}
</script>




c. CORS 해결 방안 1 : 어노테이션 이용

  • 클래스마다 설정을 해줘야 한다.
@CrossOrigin(origins = "http://localhost:8080")


  • MenuController.java : API 전체 코드
package kr.co.rland.web.controller.api;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;
import kr.co.rland.web.service.MenuService;

// API 이용하기 = AJAX

@RestController("apiMenuController")
 @CrossOrigin(origins = "http://localhost:5175")
@RequestMapping("menus")	// 예전에는 절대경로로 썼지만 지금은 "/"를 안써도 된다. 
public class MenuController {
	
	// REST API에서는 반환값이 문서를 전달하는 것이 아니라 사용자가 받는 데이터이다. 
	// List<MenuView>
//	public List<MenuView> getList(){
//		
//	}
	
	@Autowired
	private MenuService service;

	
	// 이전까지는 HTML이 제공해주는 Method만 이용했었다.
	// 이제 HTTP가 제공해주는 API를 쓸 수 있다.
	@GetMapping	// @RequestMapping과 경로가 같다면 여기는 안써도 된다.
	public List<MenuView> getList(
			@RequestParam(name = "p", defaultValue="1") int page, 	
			@RequestParam(name = "c", required=false) Integer categoryId, 	// categoryId는 무조건 사용하는 것이 아니라서 
			@RequestParam(name = "q", required=false) String query) {		// @RequestParam 이용하자!!
		// null을 담기 위해서 required false 설정을 한다. 
		List<MenuView> list = service.getViewList(page, categoryId, query);
		
		// 메인 스레드는 잠궈버리면 안된다! 그래서 콜백함수를 사용한다. 
		// 콜백함수는 이벤트를 위임하는 것이다. 미리 설정해놓고 나중에 호출한다. 
		// 콜백함수는 비동기 작업이 완료되면 호출한다.**
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return list;
	}



d. CORS 해결 방안 2**ㄴ

  • 글로벌하게 설정하는 방법 :
    • CorsConfig.java에서 WebMvcConfigurer 클래스의 WebMvcConfigurer 객체의 설정을 이용한다.
    • 이렇게 설정하는 것은 config 빈 객체를 따로 만들어서 전역에서 사용하게 해준다.(글로벌 개념)


  • 설정 스펙 :
    • Mapping 방식 : registry.addMapping/*는 루트 경로를 의미하고 /**는 루트 아래 경로의 모든 코드
    • Origins의 포트 번호 설정 : .allowedOrigins("http://localhost:5175");의 설정에다가 ‘,’로 구분지어서 여러 포트를 열어 줄 수 있다. 1개라도 쓸 수 있다.


  • 예시 코드 :
package kr.co.rland.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {
	
	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/**").allowedOrigins("http://localhost:5175");
			}
		};
	}
}



a. router-link에 대한 에러 해결

  • 절대경로 vs 상대경로 문제!

  • 아래 코드의 { path: '/admin', component: AdminLayout} 은 주인공인 주소가 아니다!

  • 즉, '/admin'은 중간 경로이다. 원래는 '/admin/index'에서 클릭해서 '/admin/menu/list'로 넘어간다.

  • 추가 개념** : router-link가 렌더링되면 a링크로 변환된다. 그래서 css로 스타일링할 때, a 태그로 스타일링한다.


b. 실습 코드 :

  • main.js

import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.
import {createRouter, createWebHashHistory} from 'vue-router';    
// 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다. 


import Layout from './components/Layout.vue';
import Index from './components/Index.vue';
import About from './components/About.vue';
import MenuList from './components/menu/List.vue';
import MenuDetail from './components/menu/Detail.vue';

import AdminLayout from './components/admin/Layout.vue';
import AdminMenuList from './components/admin/menu/List.vue';
import AdminIndex from './components/admin/Index.vue';

// 2. 여기서 Router를 매핑해준다! 
// **이거만 보더라도 router-view가 몇개인지 알 수 있다.
// (템플릿이 바뀌는 행위 숫자 = router-view 숫자 )
const routes = [

    /* index페이지만 스타일링만 달라서 레이아웃을 따로 만들어야 한다! */
    { path: '/index', component:Index}, 

    { path: '/', component: Layout, children:[
        // children으로 더 내려가는 것이 아니라 해당 레이아웃을 공유하는 것이다. 
        { path: 'menu/list', component:MenuList},
        { path: 'menu/detail', component:MenuDetail},

                // 컴포넌트 방식이 아니라 이렇게 템플릿을 간단히 쓸 수도 있다.(예전 방식)
        { path: 'about', component:About}
    ] },
    
    // { path: '/admin', component: AdminLayout} 은 주인공인 주소가 아니다!
    // '/admin'은 중간 경로이다. 원래는 '/admin/index'에서 클릭해서 '/admin/menu/list'로 넘어간다. 
    { path: '/admin', component: AdminLayout, children:[
        { path: 'index', component:AdminIndex },
        { path: 'menu/list', component:AdminMenuList }
    ] }
]
  
// 1. 여기서 Router를 생성해서 
const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    // router 기록!
    history: createWebHashHistory(),
    routes // short for `routes: routes`
})


// Vue.createApp()에서 변경!!
// createApp는 MVC를 지원하는 라이브러리이다.
// JS로 만든 모델을 mount에 라이브러리로 입혀준다.
createApp(App)
.use(router)    // App.vue 파일 내부의 router-view 태그에서 Layout과 Admin의 Layout이 교체된다.
.mount("#app");

// .vue파일은 Vue가 제공해주는 변환 엔진기이다.
// .vue 파일이 없다면 export default의 모듈을 이용 해야한다.


// Vue
// .createApp()
  


  • Layout.vue
    • 원래는 Header랑 Footer를 따로 파일에 만들어야하지만 예제 코드라서 추가하여 간단하게 만들었다.
<style>

/* router-link가 렌더링되면 a링크로 변환!!*/
a {
    color: blue;
}

</style>

<template>
    <header>
        <h1>관리자 페이지 헤더</h1>
        <ul>
            <li><router-link to="category/list">카테고리관리</router-link></li>
            <li><router-link to="menu/list">메뉴관리</router-link></li>
        </ul>
    </header>
    <router-view></router-view>
    <footer>
        footer
    </footer>
</template>


  • Index.vue(임시로 만들었다.)

<template>
   sdfdsfs
</template>


  • List.vue(Admin의 List이다.)

<template>
    <main layout:fragment="main" class="main-padding-none">   
      <section id="main-section">
         <header class="search-header">
            <h1 class="text-title1-h1">알랜드 메뉴</h1>
            <form>
               <input type="text">
               <input type="submit" class="icon icon-find">
            </form>
         </header>

         <nav class="menu-category">
            <header class="d-flex">
               <h1 class="text-normal-bold">메뉴분류</h1>
               <div>                  
                  <a class="btn btn-mini bg-blue" href="">분류추가</a>
               </div>
            </header>
            <ul>
               <li class="menu-selected">
                  <a href="/menu/list">전체</a>
               </li>
               <li>
                  <a href="">커피음료</a>
               </li>
               <li data-id="2">
                  <a href="">수제청</a>
               </li>
               <li>
                  <a href="">샌드위치</a>
               </li>
               <li>
                  <a href="">쿠키</a>
               </li>
            </ul>
         </nav>

         <section class="cart-section">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료</span>            
            <a class="btn btn-mini bg-blue" href="">메뉴추가</a>            
         </section>

      
         <section class="menu-section">
              <h1 class="d-none">메뉴목록</h1>
              <div class="menu-list">
               <section class="menu menu-reg-section border-bottom border-color-1">
                      <form class="overflow-hidden">
                          <h1><input type="text" class="input w-75 w-100-md" name="name" placeholder="메뉴 이름을 입력하세요."></h1> 
                          <div class="menu-img-box">
                        <img src="/image/product/blank-img.png" class="img-input">
                        <input type="file" class="d-none">
                          </div>
                          <div class="menu-price"><input class="w-75 w-50-md input ml-0 ml-1-md" type="text" placeholder="가격을 입력하세요"> </div>
                          <div class="menu-option-list">
                              <span class="menu-option">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>ICED</label>
                              </span>
                              <span class="menu-option ml-2">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>Large</label>
                              </span>
                          </div>
                     <div class="menu-button-list">                        
                              <input class="btn btn-line btn-round btn-size-1 btn-bd-blue rounded-0-md" type="submit" value="취소">
                              <input class="btn btn-fill btn-round rounded-0-md btn-size-1 btn-bd-blue btn-color-blue ml-1" type="submit" value="저장">
                          </div>
                      </form>
                  </section>
                  
                  <section class="menu border-bottom border-color-1">
                      <form class="">
                          <h1>aaa</h1> 
                          <div class="menu-img-box">
                              <a href="detail?id=617"><img class="menu-img" src="/image/product/12.png"></a>
                          </div>    
                          <div class="menu-price">4500 </div>
                          <div class="menu-option-list">
                              <span class="menu-option">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>ICED</label>
                              </span>
                              <span class="menu-option ml-2">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>Large</label>
                              </span>
                          </div>
                     <div class="menu-button-list">
                        <input class="btn btn-line btn-round btn-size-1 rounded-0-md" type="submit" value="수정">
                              <input class="btn btn-fill btn-round rounded-0-md btn-size-1 ml-1" type="submit" value="삭제">
                          </div>
                      </form>
                  </section>
                  
                  
              </div>
          </section>

         <div class="d-flex justify-content-center py-3">
            <a href="" class="btn btn-line btn-round w-100 w-50-md py-2">더보기(13+)</a>
         </div>

         <section class="new-menu menu-section-p">
            <h1 class="d-none">신메뉴 목록</h1>
            <!-- <ul>
                    <li>
                    </li>
                </ul>  -->
            <div class="list">
               <span>신규로 출시된 메뉴가 없습니다.</span>
            </div>
         </section>

      </section>
   </main> 
</template>

-




5) JS ES6의 모듈 export 복습

  • 아래의 최종 실습 코드를 비교하면서 아래 개념을 이해하기

  • 우리가 Vue.js 라이브러리를 가져오는 부분에서 이해 안 가는 부분 :

    • 여기서 import {createRouter, createWebHashHistory} from 'vue-router'; 의 {}가 이해가 안 간다.

import App from './App.vue';
import {createApp} from 'vue';   // 이것을 쓰면, cdn에 올라가 있는 vue 라이브러리를 가져온다.
import {createRouter, createWebHashHistory} from 'vue-router';    
// 우리는 모듈을 사용하므로! 글로벌 객체가 아니라 cdn에서 뽑아서 써야한다. 


a. export 고립화

  • export는 JS에서 고립도를 줄 수 있는 중요한 요소이다.

  • export default가 import 시킬 때, 기본 모듈로 나간다.

  • default가 안 붙은 나머지 모듈은 {}와 as로 예명을 사용해야 한다.

  • export는 객체, 클래스, 변수 등등 모든 것을 export할 수 있다.


b. importmap 개념

  • importmap을 사용하면, 모듈을 가져오는 from 부분에 변수명이나 파일명을 직접 안 써도 된다!

  • imports를 쓰면 예명으로 간단히 모듈을 가져올 수 있다.

  • 예시로 우리는 Vue를 모듈로 사용하기 위해 Vue 엔진을 번들링 모드로 사용해야만 했다. 이것은 cdn(cdnjs)에 올라가있는 vue 라이브러리를 이용했다.


c. Dynamic module loading 개념

  • import를 함수(조건)를 통해서 JS 코드에 동적으로 로딩할 수 있다.

  • 이것은 fetch와 같은 역할로서 사용할 수 있다.

  • 즉, 조건에 맞을 때만 라이브러리만 가져올 수 있다. 이것은 효율적으로 라이브러리가 불필요할 때는 안 가져온다.

  • 주로 사용 하는 곳** : 모바일과 앱을 웹 환경에서 테스트 및 디버깅 할때 sqlite 기능 사용할수 없어 죽는 문제 때문에, 조건부로 import 하는 것에 주로 사용된다.

  • Dynamic module loading 참고 사이트


a) 예제 코드 : 사용하는 방법

import('/modules/myModule.js')
  .then((module) => {
    // Do something with the module.
  });
  

let modulePath = prompt("어떤 모듈을 불러오고 싶으세요?");

import(modulePath)
  .then(obj => <모듈 객체>)
  .catch(err => <로딩 에러, e.g. 해당하는 모듈이 없는 경우>)



d. 최종 실습 코드

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="importmap">
        {
            "imports": {
              "vue": "./module1.js",
              "vue-router": "./module2.js"
            }
        } 
    </script>
    <script type="module" src="app.js"></script>
</head>
<body>
    
</body>
</html>


  • app.js
// import mltest, {test1 as mltest1} from './module1.js';
// import {test, test1} from './module2.js';
import mltest, {test1 as mltest1} from "vue";
// import {test, test1} from './module2.js';


// export는 객체 클래스 변수 등등 모든 것을 export 할 수 있다.

mltest();
mltest1();

// 1. [importmap]
// 변수명이나 파일명을 안써도 된다!
// imports를 쓰면 예명으로 간단히 가져올 수 있다.

// 2. [Dynamic import]
// import를 함수를 통해 JS 코드를 동적으로 로딩할 수 있다. 
// 이것은 fetch와 같은 역할로서 사용할 수 있다. 
// 즉, 조건에 맞을 때만 라이브러리만 가져올 수 있다. 불필요할 때는 안가져온다. 


  • module1.js
// export는 고립도를 줄 수 있는 중요한 요소이다. 
// export default가 import 시킬 때 기본 모듈로 나간다.
// 나머지 모듈은 {}로 사용해야 한다.

export default function test(){
    console.log("test");
}

// 나머지 모듈은 {}로 사용해야 한다.
export function test1(){
    console.log("test1");
}


  • module2.js
// export는 고립도를 줄 수 있는 중요한 요소이다. 
// export default가 import 시킬 때 기본 모듈로 나간다.
// 나머지 모듈은 {}로 사용해야 한다.
export default function test(){
    console.log("module2 test");
}

// export로 공개하고 싶은것만 공개할 수 있다.
export function test1(){
    console.log("module2 test1");
}

function test2(){
    console.log("test2");
}

// 이런 식으로도 export를 할 수도 있다.
// export {test1,test2}




5. 230407

1) Entity 개념

a. VO

  • ‘Value Object’라고 부른다.

  • 범위가 엄청 크다. 값을 담으므로 범용적으로 사용

  • 하수 : 그냥 구분하지 않는 경우



b. Model

  • 출력할 곳에 바인딩한다.

  • Entity와 같은 말이다.

  • DB에서 정규화하면 실질적으로 다뤄야할 값의 집합 덩어리

  • 정규화된 ‘데이터 덩어리’이다.

  • 릴레이션을 ‘테이블’이라고 부른다.

  • 테이블 단위가 Entity이다.

  • Table 5개이면 Entity도 5개(view도 개수에 포함한다.)

  • 더 큰 범위에서 사용하면



c. DTO

  • ‘Data Transfer Object’라고 부른다.

  • 원격에 해당 데이터가 포함된다. 즉, 원격에 데이터를 주고 받을 때, DTO를 사용한다.(JPA 하이버네티스 개념)

  • 클라이언트가 Proxy를 갖고 있고 서버가 Stock을 갖고 있다.

  • 원격의 DB를 가져올 때, 소켓을 사용했는데 이 개념이랑 같다.

  • Proxy(대리자)를 통해 데이터를 넘겨주고 받는다.

  • 컨트롤러가 반환 시, dto로 반환해도 된다.

  • Entity와 구조가 같은 경우도 있다.

  • 그럼에도 DTO는 필요하다. 보통 입출력할 때, 사용한다.

  • Entity가 아니라 DTO는 Entity보다 더 크게 만들어서 다른 데이터 구조로 만들어서 보낸다.



d. Domain 개념




2) vue 페이지 이동방법 1

a. 페이지 이동하는 방법

  • http://localhost/menu/detail?id=2 : 쿼리 값이라서 보통 프레임워크에서 지원해준다.
  • http://localhost/menus/2 : API url을 새로 만들기(더 복잡하다)


b. list 페이지에서 쿼리 값 만들기

a) 기본 방식(a 태그)

  <h1></h1> 
  <div class="menu-img-box">                      <!-- :src="`/image/product/${m.img}`"> -->
      <a :href="'/detail?id='+m.id"><img class="menu-img" :src="'/image/product/'+m.img"></a>
  </div>    
  <div class="menu-price"></div>
  <div class="menu-option-list">
      <span class="menu-option">
          <input class="menu-option-input" type="checkbox">
          <label>ICED</label>
      </span>            
      <span class="menu-option ml-2">
          <input class="menu-option-input" type="checkbox">
          <label>Large</label>
      </span>
  </div>


  • :to="'/detail?id='+m.id"를 이용해야 한다.
 <section class="menu-section">
      <h1 class="d-none">메뉴목록</h1>
      <div class="menu-list">
          <section class="menu" v-for="m in list">
              <form class="">
                  <h1></h1> 
                  <div class="menu-img-box">                      <!-- :src="`/image/product/${m.img}`"> -->
                    
                      <router-link :to="'detail?id='+m.id"><img class="menu-img" :src="'/image/product/'+m.img"></router-link>
                  </div>    
                  <div class="menu-price"></div>
                  <div class="menu-option-list">
                      <span class="menu-option">
                          <input class="menu-option-input" type="checkbox">
                          <label>ICED</label>
                      </span>            
                      <span class="menu-option ml-2">
                          <input class="menu-option-input" type="checkbox">
                          <label>Large</label>
                      </span>
                  </div>
                  <div class="menu-button-list">
                      <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
                      <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
                  </div>
              </form>
          </section>
      </div>
  </section>


c. detail 페이지로 query 값 가져오기

  • id:this.$route.query.id에서 router의 환경 변수인 $route를 이용하자!(넘겨 받은 값!)

<script>
    export default{
        data(){
            return{
                id:this.$route.query.id
            };
        }
    }
</script>

<template>
       <main class="main-padding-none">
      <section>
         <h1 class="d-none">알랜드 메뉴 상세정보</h1>

         <section class="cart">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료: </span>
            <div class="icon icon-basket">1</div>
         </section>
</template>


  • GET 요청인경우 굳이 헤더 설정이 필요없다.
  • mounted() 함수를 바로 이용하는 이유는 script 코드가 실행되자마자 로드 된다.
    • 생성되어서 화면에 붙여줄 때, 실행된다.
<style scoped>
    @import url(/css/component/menu-detail.css);
    @import url(/css/component/nutrition-info.css);
</style>

<script>
    export default{
        data(){
            return{
                // 모델이 아닌 것을 여기에 두면 오버헤드가 발생된다. 그래서 그냥 바로 사용하자!
                // id:this.$route.query.id
            };
        },
        mounted(){

            fetch(`http://localhost:8080/menus/${this.$route.query.id}`)
            .then(response => response.text())
            .then(result => console.log(result))
            .catch(error => console.log('error', error));
        }
    }
</script>

<template>
       <main class="main-padding-none">
      <section>
         <h1 class="d-none">알랜드 메뉴 상세정보</h1>

         <section class="cart">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료: </span>
            <div class="icon icon-basket">1</div>
         </section>


d. detail 페이지 구현하는 과정

  • id:this.$route.query.id에서 router의 환경 변수인 $route를 이용하자!(넘겨 받은 값!)

  • mounted() 함수를 바로 이용하는 이유는 script 코드가 실행되자마자 로드 된다.
    • 생성되어서 화면에 붙여줄 때, 실행된다.
  • 그래서 created에서 데이터를 만들어오고 mounted에서 데이터를 붙여서 화면에 붙인다.

  • 근데 created도 안 된다. 에러가 발생한다.

  • beforeCreate는 가능한가? 이것도 안 된다.



a) Vue 라이프 사이클 다시 정리
  • 데이터를 바인드 하기 전에 id를 가지고 와야 한다.!! 그래서 beforeCreate나 created에서 id를 가져온다.

  • 그러나, 우리는 화면에 데이터가 떠있어야 그 데이터를 사용할 수 있다. 그래서 mounted에서 id를 가져온다.

  • 높이나 너비를 바꾸기 위해서 데이터가 화면에 보여주고 있어야 값을 변경할 수 있다. 그래서 mounted에서 데이터를 가져온다.

  • 이 상황에서 에러가 안나려면 그 다음에 또 다른 동작이 꼭 필요하다.

  • 그래서 if문으로 데이터가 있을 때만 출력해주고 그것을 updated 라이프 사이클에서 확인할 수 있다. -> 이것이 re-active로 데이터가 동적으로 동작한다.

  • 처음에 menu가 null인데 mounted 되고 요청 데이터에서 데이터를 가지고 오면 그때 menu에 데이터가 담겨서 updated가 된다.

  • beforeCreate에서는 menu가 undefined이다. 아예 menu가 생기기 전이기 때문이다.

  • 정리 1** : Creation 단계는 서버 렌더링 단계라서 아직 클라이언트 렌더링(Vue 엔진)이 되지 않은 상태이다. 미리 setting 해놓는 값이다.

  • 정리 2** : Mouting 단계는 클라이언트 렌더링 단계라서 Vue 엔진이 사용되어 화면에 보여줄 수 있게 데이터를 화면에 붙여준다.




b) 페이지 이동하는 경우, 데이터를 받기 위해 조건 ‘처리하는’ 경우 :
  • 조건 처리할 때, 전체를 해줄 것인지 부분만 해줄 것인지 판단해주기!


  • v-if는 데이터가 있으면 데이터를 보여주고 데이터가 없으면 그 보여주는 블럭을 빼버린다(처리가 아예 안 된다.)


  • v-show는 데이터가 있을 때만 보여주고 데이터가 없으면 처리를 해준다.(보이지만 않게 하고 싶을 때 사용한다.)
    • 그래서, 에러가 발생할 가능성이 높다.



c) 페이지 이동하는 경우, 데이터를 받기 위해 조건 ‘처리 안 하는’ 경우 :
  • menu를 null이 아니라 {}로 객체로 설정하면, v-if의 조건 처리를 안 해도 된다.

<style scoped>
    @import url(/css/component/menu-detail.css);
    @import url(/css/component/nutrition-info.css);
</style>

<script>
    export default{
        data(){
            return{
                // 1. 모델이 아닌 것을 여기에 두면 오버헤드가 발생된다. 그래서 그냥 바로 사용하자!
                // id:this.$route.query.id
                
                // 2. 모델이 null이라면 문제가 발생한다. mounted가 나중에 되면 에러가 발생한다!
                // 원인 : 
                // 결론 : 따라서, null 값으로 초기화 하면 안된다.

                menu:{}

            };
        },
        beforeCreate(){
            console.log(`beforeCreate menu: ${this.menu}`); 
            // 데이터를 바인드 하기 전에 id를 가지고 와야 한다.!! 그래서 beforeCreate나 created에서 id를 가져온다.
            // 그러나, 우리는 화면에 데이터가 떠있어야 그 데이터를 사용할 수 있다. 그래서 mounted에서 id를 가져온다. 
            // 높이나 너비를 바꾸기 위해서 데이터가 화면에 보여주고 있어야 값을 변경할 수 있다. 그래서 mounted에서 데이터를 가져온다.
            // 이 상황에서 에러가 안나려면 그 다음에 또 다른 동작이 꼭 필요하다.

        },
        created() {
            console.log(`created menu: ${this.menu}`); 
        },
        beforeMount() {
            console.log(`beforeMount menu: ${this.menu}`); 
        },
        mounted() {
            console.log(`mounted menu: ${this.menu}`); 
            fetch(`http://localhost:8080/menus/${this.$route.query.id}`)
            .then(response => response.json())
            .then(menu => this.menu=menu)
            .catch(error => console.log('error', error));
        },
        updated() {
            console.log(`updated menu: ${this.menu}`); 
        },

    }
</script>

<template>
       <main class="main-padding-none">
      <section>
         <h1 class="d-none">알랜드 메뉴 상세정보</h1>

         <section class="cart">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료: </span>
            <div class="icon icon-basket">1</div>
         </section>

         <section>
            <div class="menu-detail">
               <header>
                  <h1 class="d-none">수제청</h1>
                  <div class="img-div">
                     <!-- <img alt="" th:src="'/image/menu/'+${menu.img}" src="https://images.unsplash.com/photo-1515442261605-65987783cb6a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"> -->
                     <img alt="" src="https://images.unsplash.com/photo-1515442261605-65987783cb6a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80">
                  </div>
               </header>
               <article>
                  <header>
                     <h1 class="text-title2"></h1>
                     <span class="text-normal"></span>
                  </header>
                  <p class="text-normal">
                  </p>
               </article>

            </div>
         </section>
      </section>

   </main>
</template>



d) 추가 개념 : v-show 개념
  • 데이터를 비즈니스 로직에서 굳이 안 보여주고 싶을 때, 사용한다.
<style scoped>
    @import url(/css/component/menu-detail.css);
    @import url(/css/component/nutrition-info.css);
</style>

<script>
    export default{
        data(){
            return{
                // 1. 모델이 아닌 것을 여기에 두면 오버헤드가 발생된다. 그래서 그냥 바로 사용하자!
                // id:this.$route.query.id
                
                // 2. 모델이 null이라면 문제가 발생한다. mounted가 나중에 되면 에러가 발생한다!
                // 원인 : 
                // 결론 : 따라서, null 값으로 초기화 하면 안된다.

                menu:null

            };
        },
        beforeCreate(){
            console.log(`beforeCreate menu: ${this.menu}`); 
            // 데이터를 바인드 하기 전에 id를 가지고 와야 한다.!! 그래서 beforeCreate나 created에서 id를 가져온다.
            // 그러나, 우리는 화면에 데이터가 떠있어야 그 데이터를 사용할 수 있다. 그래서 mounted에서 id를 가져온다. 
            // 높이나 너비를 바꾸기 위해서 데이터가 화면에 보여주고 있어야 값을 변경할 수 있다. 그래서 mounted에서 데이터를 가져온다.
            // 이 상황에서 에러가 안나려면 그 다음에 또 다른 동작이 꼭 필요하다.

        },
        created() {
            console.log(`created menu: ${this.menu}`); 
        },
        beforeMount() {
            console.log(`beforeMount menu: ${this.menu}`); 
        },
        mounted() {
            console.log(`mounted menu: ${this.menu}`); 
            fetch(`http://localhost:8080/menus/${this.$route.query.id}`)
            .then(response => response.json())
            .then(menu => this.menu=menu)
            .catch(error => console.log('error', error));
        },
        updated() {
            console.log(`updated menu: ${this.menu}`); 
        },

    }
</script>

<template>
       <main class="main-padding-none">
      <section>
         <h1 class="d-none">알랜드 메뉴 상세정보</h1>

         <section class="cart">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료: </span>
            <div class="icon icon-basket">1</div>
         </section>

         <section>
            <div class="menu-detail">
               <header>
                  <h1 class="d-none">수제청</h1>
                  <div class="img-div">
                     <!-- <img alt="" th:src="'/image/menu/'+${menu.img}" src="https://images.unsplash.com/photo-1515442261605-65987783cb6a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"> -->
                     <img alt="" src="https://images.unsplash.com/photo-1515442261605-65987783cb6a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80">
                  </div>
               </header>
               <article>
                  <header>
                     <h1 class="text-title2" v-if="menu"></h1>
                     <span class="text-normal" v-show="menu"></span>
                  </header>
                  <p class="text-normal" v-show="menu">
                  </p>
               </article>

            </div>

      </section>

   </main>
</template>



3) vue 페이지 이동방법 2

  • 경로를 파라미터로 사용한다.


a. 실습 코드

  • List.vue
    • <router-link :to="'./'+m.id">에서 경로를 id값만 사용한다.
 <section class="menu-section">
      <h1 class="d-none">메뉴목록</h1>
      <div class="menu-list">
          <section class="menu" v-for="m in list">
              <form class="">
                  <h1></h1> 
                  <div class="menu-img-box">                      <!-- :src="`/image/product/${m.img}`"> -->
                    
                      <router-link :to="'./'+m.id"><img class="menu-img" :src="'/image/product/'+m.img"></router-link>
                  </div>    
                  <div class="menu-price"></div>
                  <div class="menu-option-list">
                      <span class="menu-option">
                          <input class="menu-option-input" type="checkbox">
                          <label>ICED</label>
                      </span>            
                      <span class="menu-option ml-2">
                          <input class="menu-option-input" type="checkbox">
                          <label>Large</label>
                      </span>
                  </div>
                  <div class="menu-button-list">
                      <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
                      <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
                  </div>
              </form>
          </section>
      </div>


  • Detail.vue
<style scoped>
    @import url(/css/component/menu-detail.css);
    @import url(/css/component/nutrition-info.css);
</style>

<script>
    export default{
        data(){
            return{
                // 1. 모델이 아닌 것을 여기에 두면 오버헤드가 발생된다. 그래서 그냥 바로 사용하자!
                // id:this.$route.query.id
                
                // 2. 모델이 null이라면 문제가 발생한다. mounted가 나중에 되면 에러가 발생한다!
                // 원인 : 
                // 결론 : 따라서, null 값으로 초기화 하면 안된다.

                menu:null

            };
        },
        beforeCreate(){
            console.log(`beforeCreate menu: ${this.menu}`); 
            // 데이터를 바인드 하기 전에 id를 가지고 와야 한다.!! 그래서 beforeCreate나 created에서 id를 가져온다.
            // 그러나, 우리는 화면에 데이터가 떠있어야 그 데이터를 사용할 수 있다. 그래서 mounted에서 id를 가져온다. 
            // 높이나 너비를 바꾸기 위해서 데이터가 화면에 보여주고 있어야 값을 변경할 수 있다. 그래서 mounted에서 데이터를 가져온다.
            // 이 상황에서 에러가 안나려면 그 다음에 또 다른 동작이 꼭 필요하다.

        },
        created() {
            console.log(`created menu: ${this.menu}`); 
        },
        beforeMount() {
            console.log(`beforeMount menu: ${this.menu}`); 
        },
        mounted() {
            console.log(`mounted menu: ${this.menu}`); 
		
			
            // fetch(`http://localhost:8080/menus/${this.$route.query.id}`)
            
            // 여기가 중요하다!! query가 아니라 params이다.
            fetch(`http://localhost:8080/menus/${this.$route.params.id}`)	
            .then(response => response.json())
            .then(menu => this.menu=menu)
            .catch(error => console.log('error', error));
        },
        updated() {
            console.log(`updated menu: ${this.menu}`); 
        },

    }
</script>



4) rland 신규 메뉴 목록의 조회 추가하기

  • Component 사이에 관계를 가지는 것
    • 3가지 도구 : 속성, 메서드, 이벤트
    • 이렇게 3가지 도구가 있으면 각강의 컴포넌트 사이에 데이터를 주고 받을 수 있다.


a. 기본 세팅 :

  • import와 component를 사용한다.

  • List.vue


<script>
import NewMenuList from './NewMenuList.vue';

export default {
    data() {
        return {
            list: []
        };
    },
    mounted() {
        fetch("http://localhost:8080/menus")
            .then(response => response.json())
            .then(list => { this.list = list; });
    },
    methods: {},
    components: { NewMenuList }
}
</script>


<style>
    @import url(/css/component/menu-section.css);
    @import url(/css/component/aside.css);
    @import url(/css/component/cart-section.css);
    @import url(/css/component/menu-category.css);
    @import url(/css/component/search-header.css);
</style>

<template>

    <!-- ---------------------------------------------------------------------- -->   
   <main>
      <section>
         <header class="search-header">
            <h1 class="text-title1-h1">알랜드 메뉴</h1>
            <form>
               <input type="text">
               <input type="submit" class="icon icon-find">
            </form>
         </header>
         <aside class="aside-bar">
            <h1>aside</h1>
            <section class="aside-bar-content">
               <h1>메인메뉴</h1>
               <ul class="mt-3">
                  <li><a href="">카페메뉴</a></li>
                  <li><router-link to="/admin/menu/list">공지사항</router-link></li>
                  <li><a href="/user/login.html">로그인</a></li>
               </ul>
            </section>
         </aside>
         <nav class="menu-category">
            <div>
               <h1 class="text-normal-bold">메뉴분류</h1>
            </div>
            <ul>
               <li class="menu-selected">
                  <router-link to="">전체</router-link>
               </li>
               <li>
                <router-link to="">커피음료</router-link>
               </li>
               <li>
                  <router-link to="">수제청</router-link>
               </li>
               <li>
                  <router-link to="">샌드위치</router-link>
               </li>
               <li>
                  <router-link to="">쿠키</router-link>
               </li>
            </ul>
         </nav>

         <section class="cart-section">
            <h1 class="d-none">장바구니</h1>
            <span class="text-title3">커피음료</span>
            <div class="icon icon-basket icon-text">1</div>
         </section>

         <section class="menu-section">
              <h1 class="d-none">메뉴목록</h1>
              <div class="menu-list">
                  <section class="menu" v-for="m in list">
                      <form class="">
                          <h1></h1> 
                          <div class="menu-img-box">                      <!-- :src="`/image/product/${m.img}`"> -->
                            
                              <router-link :to="'./'+m.id"><img class="menu-img" :src="'/image/product/'+m.img"></router-link>
                          </div>    
                          <div class="menu-price"></div>
                          <div class="menu-option-list">
                              <span class="menu-option">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>ICED</label>
                              </span>            
                              <span class="menu-option ml-2">
                                  <input class="menu-option-input" type="checkbox">
                                  <label>Large</label>
                              </span>
                          </div>
                          <div class="menu-button-list">
                              <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
                              <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
                          </div>
                      </form>
                  </section>
              </div>
          </section>

         <div class="d-flex justify-content-center py-3">
            <a href="" class="btn btn-round w-100 w-50-md py-2">더보기(13+)</a>
         </div>
         <NewMenuList/>
      </section>
   </main> 
</template>


  • NewMenuList.vue
<template>
    <section class="new-menu menu-section-p">
        <h1 class="d-none">신메뉴 목록</h1>
        <!-- <ul>
                <li>
                </li>
            </ul>  -->
        <div class="list">
            <span>신규로 출시된 메뉴가 없습니다.</span>
        </div>
    </section>
</template>


b. List 페이지의 ‘NewMenuList’가 붙었는데 이것의 다른 컴포넌트는 서로 상호작용을 해야 한다.

  • 각각의 컴포넌트가 같이 동작하면, 각각의 컴포넌트에서 동기화가 어려워진다.

  • 따라서, 한 번에 객체에 담아서 사용하는 것이 좋다.

  • 신규 메뉴 목록은 기존 메뉴 항목과 같이 한 번에 보여지기 때문에 데이터를 합쳐서 한 번에 보낸다. 이것은 ‘Vue에 바인딩되는 데이터’이기 때문에 SSR에서 이 개념을 ‘model’로도 볼 수 있다.

  • 정리** : ‘원격’의 ‘Vue’로 가는 ‘model’이라서 ‘DTO’라고 부른다.


a) DTO 이용하기 = 첫 번째 방법
  • MenuListData.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuListData {
	
	private List<MenuView> list;
	private List<MenuView> newMenuList;
}


  • MenuController.java

// API 이용하기 = AJAX

@RestController("apiMenuController")
// @CrossOrigin(origins = "http://localhost:5175")
@RequestMapping("menus")	// 예전에는 절대경로로 썼지만 지금은 "/"를 안써도 된다. 
public class MenuController {
	
	// REST API에서는 반환값이 문서를 전달하는 것이 아니라 사용자가 받는 데이터이다. 
	// List<MenuView>
//	public List<MenuView> getList(){
//		
//	}
	
	@Autowired
	private MenuService service;

	
	// 이전까지는 HTML이 제공해주는 Method만 이용했었다.
	// 이제 HTTP가 제공해주는 API를 쓸 수 있다.
	@GetMapping	// @RequestMapping과 경로가 같다면 여기는 안써도 된다.
	public MenuListData getList(
			@RequestParam(name = "p", defaultValue="1") int page, 	
			@RequestParam(name = "c", required=false) Integer categoryId, 	// categoryId는 무조건 사용하는 것이 아니라서 
			@RequestParam(name = "q", required=false) String query) {		// @RequestParam 이용하자!!
		// null을 담기 위해서 required false 설정을 한다. 
		List<MenuView> list = service.getViewList(page, categoryId, query);
		
		// DTO 추가!!**
		List<MenuView> newMenuList = service.getViewList(page, categoryId, query);
		
		// 1. 사용자에게 JSON 데이터를 담아주는 첫번째 방법 
		// 이렇게 아래 코드처럼 임시 객체로 쓰는 것이 아니라 Map 콜렉션이나 ArrayList 콜렉션 형태로 사용해도 된다. 
		// 여러 번 사용하는 경우 Map에 담아서 사용자에게 JSON 데이터를 담아서 보내주기 위해 사용한다.
		MenuListData data = new MenuListData();
		data.setList(list);
		data.setNewMenuList(list);
		
		return data;
	}
	
	



b) DTO 이용하기 = 두 번째 방법
  • 두 번째 방법 : 데이터를 전달하기 위해서 임의의 객체를 사용하지 않는 경우.

  • 이렇게 Map 객체로 여러 객체를 API로 넘기면, API의 개수도 줄일 수 있다.


  • MenuController.java

// API 이용하기 = AJAX

@RestController("apiMenuController")
@RequestMapping("menus")	// 예전에는 절대경로로 썼지만 지금은 "/"를 안써도 된다. 
public class MenuController {
	
	// REST API에서는 반환값이 문서를 전달하는 것이 아니라 사용자가 받는 데이터이다. 
	// List<MenuView>
//	public List<MenuView> getList(){
//		
//	}
	
	@Autowired
	private MenuService service;

	
	// 이전까지는 HTML이 제공해주는 Method만 이용했었다.
	// 이제 HTTP가 제공해주는 API를 쓸 수 있다.
	@GetMapping	// @RequestMapping과 경로가 같다면 여기는 안써도 된다.
	public Map<String, Object> getList(
			@RequestParam(name = "p", defaultValue="1") int page, 	
			@RequestParam(name = "c", required=false) Integer categoryId, 	// categoryId는 무조건 사용하는 것이 아니라서 
			@RequestParam(name = "q", required=false) String query) {		// @RequestParam 이용하자!!
		
		// null을 담기 위해서 required false 설정을 한다. 
		List<MenuView> list = service.getViewList(page, categoryId, query);
		
		// DTO 추가!!**
		List<MenuView> newList = service.getViewList(page, categoryId, query);
		// 원래는 서비스단을 추가해줘야 한다. 
		
		// 1. 사용자에게 JSON 데이터를 담아주는 첫 번째 방법 :
		// - 이렇게 아래 코드처럼 임시 객체로 쓰는 것이 아니라 Map 콜렉션이나 ArrayList 콜렉션 형태로 사용해도 된다. 
		// - 여러 번 사용하는 경우 Map에 담아서 사용자에게 JSON 데이터를 담아서 보내주기 위해 사용한다.
		//	MenuListData data = new MenuListData();
		//	data.setList(list);
		//	data.setNewMenuList(list);

		// 2. 사용자에게 JSON 데이터를 담아주는 두 번째 방법 :
		// - 데이터를 전달하기 위해서 임의의 객체를 사용하지 않는 경우
		// - 이렇게 넘기면, API의 개수를 줄일 수 있다.
		Map<String, Object> data = new HashMap<>();
		data.put("list", list);
		data.put("newMenuList", newList);
		
		return data;
	}
	


c. API의 AJAX 처리

  • 아래 코드처럼 해당 URL을 요청해서 데이터를 받아와서 사용한다.

<script>
import NewMenuList from './NewMenuList.vue';

export default {
    data() {
        return {
            list: [],
            newList:[]
        };
    },
    mounted() {
        fetch("http://localhost:8080/menus")
            .then(response => {
               return response.json()
            })
               .then(data =>{ 
                  this.list = data.list; 
                  this.newList = data.newList;
               } );
    },
    methods: {},
    components: { NewMenuList }
}
</script>