화면단 구성 이전에 디자인이 너무 사용하기 싫게 생겨서 부트스트랩에서 템플릿을 하나 다운받았다. 전체 템플릿을 쓰기에는 너무 커서 헤더 부분만 따와서 진행했다.
.start-screen {
background-image: url('../assets/Sexy Blue.jpg');
background-size: cover;
background-position: center;
}
.header-font {
font-family: 'Lilita One', cursive;
color: white;
position: relative;
}
.top-right-text {
position: absolute; /* 상대 위치 요소에 대해 절대 위치로 설정 */
top: 0px; /* 위에서 0px 떨어진 위치 (오른쪽 상단) */
left: 40px; /* 오른쪽에서 0px 떨어진 위치 (오른쪽 상단) */
font-size: 70px;
}
.masthead {
padding-top: 10.5rem;
padding-bottom: 6rem;
text-align: center;
color: #fff;
background-image: url("../assets/resources/assets/img/header-bg.jpg");
background-repeat: no-repeat;
background-attachment: scroll;
background-position: center center;
background-size: cover;
}
.masthead-subheading {
font-size: 1.5rem;
font-style: italic;
line-height: 1.5rem;
margin-bottom: 25px;
font-family: "Roboto Slab", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.text-uppercase {
text-transform: uppercase !important;
}
css파일에서 내가 쓸 것들만 챙겨서 인트로 컴포넌트에 적용시키고 버튼누르면 메인페이지로 넘어가는 공간이기에 템플릿에 내용이 복잡하지는 않게 헤더만 구성했다.
<template>
<header class='masthead'>
<div class='masthead-subheading'>Welcome To Our Service!</div>
<div class='masthead-heading text-uppercase'>SEMOSE</div>
<b-col class="pb-2" >
<b-button size="lg"
style="font-size: 40px; font-weight: bold;"
variant="info"
to = "/main"
>START
</b-button>
</b-col>
</header>
</template>
다음과 같이 이미지만 바꿔서 구성했다.
부트스트랩에서 폼 형식을 제공하는데 이것을 참고해서 주소 선택 부분과 store선택 부분을 다시 만들 예정이다. 물론 미리 만든 주소 컴포넌트를 활용한다.
배경은 계속 백그라운드 이미지를 재탕해서 사용한다.
먼저 폼이 들어갈 큰 박스를 하나 만들고,
부트스트랩의 input폼을 사용했다.
주소입력 버튼을 누르면 다음주소찾기 오픈소스 메서드인 execDaumPostcode()가 실행되게끔 클릭이벤트로 넣어주었다.
<template>
<div>
<b-form v-if="show">
<div class="white-box">
<b-input-group
prepend="Address"
class="mt-3"
id="input-group-1"
label="Address"
label-for="input-1"
>
<b-form-input v-model="dataAddress"></b-form-input>
<b-input-group-append>
<b-button variant="info" @click="execDaumPostcode()">주소입력</b-button>
</b-input-group-append>
</b-input-group>
.....
주소입력을 누르면
다행히 잘 뜬다.
FormComponent에 데이터도 잘 들어옴을 확인할 수 있다. 미리 만들어둔 컴포넌트의 상,하위 컴포넌트 연결만 하니 데이터가 잘 분배되고 있다.
Select는 외부 서비스를 고르는 공간인데, 선택을하면 밑에 카드 박스에 데이터들이 버튼으로 생성되게끔 구현하려고 한다. 일단 select를 다음과 같이 구현하였다.
<b-form-group
id="input-group-2"
label="Store List"
label-for="input-2"
description="자주 이용하는 시설을 선택해주세요."
>
<b-form-select
id="input-2"
v-model="selectedOption"
:options="stores"
@input="addToSelected"
required
>
</b-form-select>
</b-form-group>
v-model은 중복허용에 대한 변수를 양방향 바인딩하였고 options는 select안에 들어갈 시설물들의 리스트이다. @input은 선택시에 addToSelected()메서드가 실행되게끔 한다.
addToSelected() {
if (!this.selected_list.includes(this.selectedOption)) {
this.selected_list.push(this.selectedOption);
}
이 메서드는 만약 selectedOption이 selected_list안에 포함되어있지 않다면, 이 리스트에 selectedOption을 추가하는 함수이다. v-model로 연결했기에 선택된 요소는 해당 변수로 들어가게 된다. 이로써 중복을 피하도록 하였다.
그 후 선택에 의해 리스트에 담긴 데이터를 하나하나 버튼으로 아래에 카드공간을 만들어 선택때마다 자신의 선택사항이 보이게 했다. 버튼으로 구현한 이유는 버튼을 클릭하면 사라지게 만들, 수정의 목적을 미리 고려했다.
<b-card-group deck>
<b-card
header="수정을 원하시면 추가된 항목버튼을 클릭해주세요."
header-tag="header"
title="CHOOSED STORES"
>
<b-button
v-for="item in selected_list"
:key="item"
variant="success"
class="mr-2"
@click="removeFromSelected(item)"
>
{{ item }}
</b-button>
</b-card>
</b-card-group>
버튼은 v-for로 하여금 자동적으로 구현되게 하였으며, 클릭시에는 removeFromSelected(item)이 실행되게 하였다. 변수명 해석대로 클릭시 리스트에서 지워지며, 리스트에서 지워진다는 것은 버튼이 사라진다는 뜻이다.
선택된 항목들이 아래에 나타나고 클릭시에 사라지는 모습을 볼 수 있다. Reset버튼은 한번에 다 버튼들을 지워주는 기능의 버튼이다. 리스트를 초기화 함으로써 모두 지운다.
# reset
<b-button variant="danger" @click="resetSelected">Reset</b-button>
.....
resetSelected() {
this.selected_list = [];
},
Submit은 주소와 버튼들을 모두 구성완료 했을 때 백엔드로 데이터를 보내기 위한 최상위 컴포넌트로 데이터를 옮겨주는 역할이다.
# Submit
<b-button variant="info" @click="updateStore">Submit</b-button>
....
updateStore() {
this.$emit("onUpdateStore", this.selected_list);
},
실제로 유저입장에서는 서브밋 버튼을 눌렀을 때 어떤 변화가 있어야 할 것 같다. 전송완료의 이벤트를 전달함과 동시에 데이터들을 모두 초기화 시킨다던지 해야할 것 같다. 부모 컴포넌트에서 받은 시설물 리스트 데이터와 주소 데이터를 이제 POST로 백엔드에 쏴주기만 하면 요청 부분은 끝난다.
남은 일은 시설물 선택 후보군을 프로젝트 범위에 맞게 늘리고, 테스트 케이스를 만들어서 인공지능 모델에 사용할 데이터를 축적해야한다. 또 맵을 띄우는 과정과 스코어 부분이 나타나는 과정이 다음으로 필요할 것이다. 이제 다시 백엔드로 돌아가야할 시간인데 디자인이 자꾸 아쉬워서 쳐다보게 되는 것 같다. 부트스트랩이 은근 옛날 느낌이라고 비추하시던 멘토님의 의견이 이제 좀 이해가는 느낌이랄까. 은근 폭이 좁고 디자인이 옛스러워서 다른 거 찾게 되는데 이런 부분은 노는 시간에 재미로 찾아보고 적용 시켜봐야 할 것 같다.
## Form 컴포넌트 전체 ###
<template>
<div>
<b-form v-if="show">
<div class="white-box">
<b-input-group
prepend="Address"
class="mt-3"
id="input-group-1"
label="Address"
label-for="input-1"
>
<b-form-input v-model="dataAddress"></b-form-input>
<b-input-group-append>
<b-button variant="info" @click="execDaumPostcode()">주소입력</b-button>
</b-input-group-append>
</b-input-group>
<b-form-group
id="input-group-2"
label="Store List"
label-for="input-2"
description="자주 이용하는 시설을 선택해주세요."
>
<b-form-select
id="input-2"
v-model="selectedOption"
:options="stores"
@input="addToSelected"
required
>
</b-form-select>
</b-form-group>
<b-card-group deck>
<b-card
header="수정을 원하시면 추가된 항목버튼을 클릭해주세요."
header-tag="header"
title="CHOOSED STORES"
>
<b-button
v-for="item in selected_list"
:key="item"
variant="success"
class="mr-2"
@click="removeFromSelected(item)"
>
{{ item }}
</b-button>
</b-card>
</b-card-group>
<b-button variant="info" @click="updateStore">Submit</b-button>
<b-button variant="danger" @click="resetSelected">Reset</b-button>
</div>
</b-form>
</div>
</template>
<script>
export default {
name: 'FormComponent',
data() {
return {
// daum post code data //
postcode: "",
dataAddress: "",
extraAddress: "",
// daum post code data //
// select data //
stores: [
{ text: 'Select', value: null },
{ text: '스타벅스', value: '스타벅스' },
{ text: '맥도날드', value: '맥도날드' },
{ text: '메가커피', value: '메가MGC커피' },
{ text: '편의점', value: '편의점' }
],
selected: null,
selected_list: [],
selectedOption: null,
// select data //
show: true,
}
},
methods: {
execDaumPostcode() {
new window.daum.Postcode({
oncomplete: (data) => {
if (this.extraAddress !== "") {
this.extraAddress = "";
}
if (data.userSelectedType === "R") {
// 사용자가 도로명 주소를 선택했을 경우
this.dataAddress = data.roadAddress;
} else {
// 사용자가 지번 주소를 선택했을 경우(J)
this.dataAddress = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if (data.userSelectedType === "R") {
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if (data.bname !== "" && /[동|로|가]$/g.test(data.bname)) {
this.extraAddress += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if (data.buildingName !== "" && data.apartment === "Y") {
this.extraAddress +=
this.extraAddress !== ""
? `, ${data.buildingName}`
: data.buildingName;
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if (this.extraAddress !== "") {
this.extraAddress = `(${this.extraAddress})`;
}
} else {
this.extraAddress = "";
}
// 우편번호를 입력한다.
this.postcode = data.zonecode;
this.$emit("onUpdateAddress", this.dataAddress);
},
}).open();
},
addToSelected() {
if (!this.selected_list.includes(this.selectedOption)) {
this.selected_list.push(this.selectedOption);
}
},
removeFromSelected(item) {
const index = this.selected_list.indexOf(item);
if (index !== -1) {
this.selected_list.splice(index, 1);
}
},
resetSelected() {
this.selected_list = [];
},
updateStore() {
this.$emit("onUpdateStore", this.selected_list);
},
},
props: {
},
}
</script>
<style>
.white-box {
background-color: #f0f0f0;
padding: 20px; /* 원하는 패딩을 적용합니다. */
border: 1px solid #f0f0f0; /* 원하는 테두리 스타일을 적용합니다. */
border-radius: 10px; /* 원하는 테두리 모서리를 적용합니다. */
/* 기타 원하는 스타일을 적용할 수 있습니다. */
}
</style>