241129 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.
뷰
코드를 짤 때 시큐어 코딩, UX / UI를 최대한 고려해서 코드를 짤려고 노력해야한다!
이전 글과 이어서 진행됩니다.
html 파일 내에 코드가 너무 길어 각 컴포넌트, 라우터 분리하기! 이클립스로 돌아가자!
static에 components, router 폴더 생성
myrouter.js
(static/router)
import {createApp} from 'vue';
import {createRouter, createWebHistory} from 'vue-router';
import AllData from "../components/alldata.js";
import BuserData from "../components/buserdata.js";
import JikwonData from "../components/jikwondata.js";
import GogekData from "../components/gogekdata.js";
// Vue Router 설정
const routes = [
{ path: "/", component: AllData },
{ path: "/busers", component: BuserData },
{ path: "/jikwons", component: JikwonData },
{ path: "/gogeks", component: GogekData },
];
// 라우터 생성
const router = createRouter({
history: createWebHistory(),
routes,
});
// Vue 앱 생성
const app = createApp({});
app.use(router);
app.mount("#app");
alldata.js
export default {
template: `
<div>
<h2>전체자료</h2>
<table v-if="allData.length">
<thead>
<tr>
<th>부서번호</th><th>부서명</th><th>직원명</th><th>관리고객명</th>
</tr>
</thead>
<tbody>
<tr v-for="data in allData" :key="data.jikwonno">
<td>{{data.buserno}}</td>
<td>{{data.busername}}</td>
<td>{{data.jikwonname}}</td>
<td>{{data.gogekname}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
allData: [],
};
},
mounted() {
axios
.get("http://localhost/joindata")
.then((response) => {
this.allData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
}
buserdata.js
export default {
template: `
<div>
<h2>부서자료</h2>
<table v-if="buserData.length">
<thead>
<tr>
<th>부서번호</th><th>부서명</th><th>위치</th><th>전화번호</th>
</tr>
</thead>
<tbody>
<tr v-for="data in buserData" :key="data.buserno">
<td>{{data.buserno}}</td>
<td>{{data.busername}}</td>
<td>{{data.buserloc}}</td>
<td>{{data.busertel}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
buserData: [],
};
},
mounted() {
axios
.get("http://localhost/busers")
.then((response) => {
this.buserData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
}
jikwondata.js
export default {
template: `
<div>
<h2>직원자료</h2>
<table v-if="jikwonData.length">
<thead>
<tr>
<th>사번</th><th>이름</th><th>직급</th><th>연봉</th>
</tr>
</thead>
<tbody>
<tr v-for="data in jikwonData" :key="data.jikwonno">
<td>{{data.jikwonno}}</td>
<td>{{data.jikwonname}}</td>
<td>{{data.jikwonjik}}</td>
<td>{{data.jikwonpay}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
jikwonData: [],
};
},
mounted() {
axios
.get("http://localhost/jikwons")
.then((response) => {
this.jikwonData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
}
gogekdata.js
export default {
template: `
<div>
<h2>고객자료</h2>
<table v-if="gogekData.length">
<thead>
<tr>
<th>고객번호</th><th>고객이름</th><th>전화</th>
</tr>
</thead>
<tbody>
<tr v-for="data in gogekData" :key="data.gogekno">
<td>{{data.gogekno}}</td>
<td>{{data.gogekname}}</td>
<td>{{data.gogektel}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
gogekData: [],
};
},
mounted() {
axios
.get("http://localhost/gogeks")
.then((response) => {
this.gogekData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
}
ajaxroute2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type="module" src="./router/myrouter.js" defer></script>
<style></style>
</head>
<body>
<div id="app">
<h1>자료보기</h1>
<nav>
<router-link to="/">전체자료</router-link> |
<router-link to="/busers">부서자료</router-link> |
<router-link to="/jikwons">직원자료</router-link> |
<router-link to="/gogeks">고객자료</router-link>
</nav>
<router-view></router-view>
</div>
</body>
</html>
에러
🚨 ajaxroute2.html:1 Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
Vue.js와 Vue Router를 CDN에서 직접 가져오는 경우:
type="module" 속성을 사용하여 모듈 방식으로 JavaScript 파일을 로드할 때는, 모듈을 CDN에서 가져오는 것이 아닌, 직접 경로를 지정해야 합니다. CDN에서 가져온 Vue.js와 Vue Router는 전역적으로 사용할 수 있지만, 모듈 방식에서는 직접 가져와야 합니다.
Vue.js와 Vue Router를 전역으로 사용하기:
myrouter.js 파일에서 Vue.js와 Vue Router를 전역으로 사용하도록 코드를 수정할 수 있습니다. 예를 들어, createApp과 createRouter를 사용할 때 전역으로 설정한 Vue를 참조하게 됩니다.
모듈을 사용할 때의 올바른 경로 지정:
myrouter.js 파일 내에서 Vue.js 및 Vue Router를 가져오는 방법을 수정합니다.
import {createApp} from 'vue';
import {createRouter, createWebHistory} from 'vue-router';
🔽
// myrouter.js 수정
const { createApp } = Vue;
const { createRouter, createWebHistory } = VueRouter;
🚨 [Vue Router warn]: No match found for location with path "/ajaxroute2.html"
작동에는 문제가 없지만 Vue Router가 ajaxroute2.html 경로를 찾지 못했다 경고가 뜬다.
잘못된 URL 접근:
실제로 애플리케이션을 실행할 때 브라우저의 주소창에 /ajaxroute2.html을 입력하여 Vue Router가 해당 경로에 대한 정의가 없기 때문에 경고가 발생했다.
정확한 경로 사용:
Vue Router를 사용했을 때 애플리케이션 내에서 적절한 링크를 사용하여 경로를 탐색해야 하겠다. Vue Router를 통해 정의된 경로만 사용해야 하며, 직접 URL을 입력하는 것은 피하는 것이 좋을 것 같다.
myrouter.js (수정)
// import {createApp} from 'vue';
// import {createRouter, createWebHistory} from 'vue-router';
import AllData from "../components/alldata.js";
import BuserData from "../components/buserdata.js";
import JikwonData from "../components/jikwondata.js";
import GogekData from "../components/gogekdata.js";
import NotFound from '../components/notfound.js';
// Vue Router 설정
const { createApp } = Vue;
const { createRouter, createWebHistory } = VueRouter;
const routes = [
{ path: "/", name: "all", component: AllData },
{ path: "/busers", name: "busers", component: BuserData },
{ path: "/jikwons", name: "jikwons", component: JikwonData },
{ path: "/gogeks", name: "gogeks", component: GogekData },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, // 해결책
];
// 라우터 생성
const router = createRouter({
history: createWebHistory(),
routes,
});
// Vue 앱 생성
const app = createApp({});
app.use(router);
app.mount("#app");
notfound.js
export default {
template: `<h3>Hi~!</h3>`,
}
결과
뷰 실습 (RESTful : CRUD)
product 테이블 생성
create table product(
code varchar(50) primary key,
name varchar(255) not null,
description text,
price int not null,
image varchar(255) default 'https://t1.daumcdn.net/daumtop_deco/images/pctop/2023/logo_daum.png');
insert into product (code,name,description,price) values ('p1','볼펜','부드럽게 글씨가 써진다.',3000);
insert into product (code,name,description,price) values ('p2','연필','사각사각 글씨가 써진다.',1500);
select * from product;
의존성 추가
application.properties
spring.application.name=sprweb50restful_vue
server.port=80
spring.thymeleaf.cache=false
#mariadb server connect
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=1111
#mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=pack.dto.**
ProductMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="product">
<select id="getAll" resultType="productDto">
select * from product
</select>
<select id="getData" parameterType="String" resultType="productDto">
select * from product where code=#{code}
</select>
<insert id="insert" parameterType="productDto">
insert into product (code,name,description,price) values(#{code},#{name},#{description},#{price})
</insert>
<update id="update" parameterType="productDto">
update product set name=#{name},description=#{description},price=#{price} where code=#{code}
</update>
<delete id="delete" parameterType="String">
delete from product where code=#{code}
</delete>
</mapper>
build.gradle
(swagger 의존성 추가)
dependencies {
...
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
}
ProductProcess
package pack.repository;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import pack.dto.ProductDto;
@Repository
public class ProductProcess {
@Autowired
private SqlSession session;
public List<ProductDto> getAll() {
return session.selectList("product.getAll"); // mapper의 namespace.id
}
public ProductDto getData(String code) {
return session.selectOne("product.getData", code);
}
public void insert(ProductDto dto) {
session.insert("product.insert", dto);
}
public void update(ProductDto dto) {
session.update("product.update", dto);
}
public void delete(String code) {
session.delete("product.delete", code);
}
}
ProductController
package pack.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.RestController;
import pack.dto.ProductDto;
import pack.repository.ProductProcess;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductProcess productProcess;
@GetMapping
public ResponseEntity<List<ProductDto>> getAll() {
List<ProductDto> products = productProcess.getAll();
return ResponseEntity.ok(products); // HTTP 응답을 받환 200
}
@PostMapping
public ResponseEntity<String> insert(@RequestBody ProductDto dto) {
productProcess.insert(dto);
return ResponseEntity.ok("상품 추가 성공");
}
@GetMapping("/{code}")
public ResponseEntity<ProductDto> getData(@PathVariable("code")String code) {
ProductDto product = productProcess.getData(code);
if(product != null) {
return ResponseEntity.ok(product); // HTTP 응답 200을 받환
} else {
return ResponseEntity.notFound().build(); // HTTP 응답 404을 받환
}
}
@PutMapping("/{code}")
public ResponseEntity<String> update(@PathVariable("code")String code,
@RequestBody ProductDto dto) {
// 수정 대상 상품 존재 여부 확인
ProductDto product = productProcess.getData(code);
if(product == null) {
return ResponseEntity.notFound().build(); // HTTP 응답 404을 받환
}
dto.setCode(code);
productProcess.update(dto);
return ResponseEntity.ok("상품 수정 성공");
}
@DeleteMapping("/{code}")
public ResponseEntity<String> delete(@PathVariable("code")String code) {
// 삭제 대상 상품 존재 여부 확인
ProductDto product = productProcess.getData(code);
if(product == null) {
return ResponseEntity.notFound().build(); // HTTP 응답 404을 받환
}
productProcess.delete(code);
return ResponseEntity.ok("상품 삭제 성공");
}
}
각 요청 작동 확인
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style></style>
</head>
<body>
<div id="app">
<nav>
<router-link to="/">자료보기</router-link> |
<router-link to="/add">자료추가</router-link> |
</nav>
<router-view></router-view>
</div>
<script type="module">
import ViewProduct from "../components/viewproduct.js";
// import AddProduct from "../components/addproduct.js";
// import EditProduct from "../components/editproduct.js";
const routes = [
{ path: "/", component: ViewProduct },
//{ path: "/add", component: AddProduct },
//{ path: "/edit/:code", component: EditProduct },
];
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
});
const app = Vue.createApp({});
app.use(router);
app.mount("#app");
</script>
</body>
</html>
viewproduct.js
export default {
template:`
<div>
<h2>제품 정보</h2>
<table>
<tr>
<th>코드</th><th>이미지</th><th>제품명</th><th>가격</th><th>설명</th><th>작업</th>
</tr>
<tr v-for="product in products" :key="product.code">
<td>{{product.code}}</td>
<td>
<img :src="product.image" alt="제품이미지" />
</td>
<td>{{product.name}}</td>
<td>{{product.price}}</td>
<td>{{product.description}}</td>
<td>
<button @click="editProduct(product.code)">수정</button> /
<button @click="deleteProduct(product.code)">삭제</button>
</td>
</tr>
</table>
</div>
`,
data(){
return {
products:[]
};
},
methods:{
fetchProducts() {
axios.get("/products")
.then(response => {
this.products = response.data;
})
.catch(error => {
console.error("읽기 오류", error);
});
}
},
created(){ // 컴포넌트가 인스턴스된 직후(랜더링 전)에 호출
this.fetchProducts()
}
}
결과
'Study > Acorn' 카테고리의 다른 글
241203 노드 (이벤트 처리, DB 연결) (0) | 2024.12.03 |
---|---|
241202 노드 (개요, CommonJs, ESM, 동기/비동기) (3) | 2024.12.02 |
241128 뷰 (ajax, ajax-router) (1) | 2024.11.28 |
241127 뷰 (이벤트, 구글 차트, route, ajax) (0) | 2024.11.27 |
241126 뷰 (이벤트) (1) | 2024.11.26 |