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 + a
후cmd + 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에서도 조건문이 가능해서 계산기 같은 것에서 사용한다. 변환해서 사용한다.
- sass에서는
- 우리는 지금껏 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()
5) link 걸어서 페이지 이동
-
Vue로 프로그래밍을 하면, 소스코드에서 코드가 안보여서 문서를 공개할 수 없다. 문서를 공개해야하는 경우는 불리하지만 앱(어플리케이션)을 만들 때는 괜찮다.
-
또한, 페이지 이동 시,
router-link
와to
를 이용한다. -
router-link
는a
태그,to
는href
의 역할을 해준다.
<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 문제
-
보통, 브라우저에서 클라이언트는 문서(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 빈 객체를 따로 만들어서 전역에서 사용하게 해준다.(글로벌 개념)
- CorsConfig.java에서
- 설정 스펙 :
- Mapping 방식 :
registry.addMapping
에/*
는 루트 경로를 의미하고/**
는 루트 아래 경로의 모든 코드 - Origins의 포트 번호 설정 :
.allowedOrigins("http://localhost:5175");
의 설정에다가 ‘,’로 구분지어서 여러 포트를 열어 줄 수 있다. 1개라도 쓸 수 있다.
- Mapping 방식 :
- 예시 코드 :
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");
}
};
}
}
4) router-link 사용 방법
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 하는 것에 주로 사용된다.
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>
b) router-link 이용
: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>