Merge branch 'search'
This commit is contained in:
commit
22f5eced3c
@ -5,9 +5,16 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script src="//dapi.kakao.com/v2/maps/sdk.js?appkey=042e55278939bfa9163a67a6fe4021c0&libraries=services,clusterer,drawing"></script>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
import { localAxios } from "@/utils/http-commons";
|
import { localAxios } from '@/utils/http-commons';
|
||||||
|
|
||||||
const local = localAxios;
|
const local = localAxios;
|
||||||
|
|
||||||
function getArticles(contentTypeId, sidoCode, gugunCode, title, success, fail) {
|
function searchAttarctions(queryString, success, fail) {
|
||||||
//list
|
//list
|
||||||
local
|
local.get(`/attraction/search?contentTypeId=12&${queryString}`).then(success).catch(fail);
|
||||||
.get(
|
|
||||||
`/attraction/search?contentTypeId=${contentTypeId}&sidoCode=${sidoCode}&gugunCode=${gugunCode}&title=${title}`
|
|
||||||
)
|
|
||||||
.then(success)
|
|
||||||
.catch(fail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getArticles };
|
export { searchAttarctions };
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
|
||||||
|
--shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
/* semantic color variables for this project */
|
||||||
@ -118,6 +120,10 @@ textarea {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@import './base.css';
|
@import './base.css';
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ const logout = () => {
|
|||||||
<RouterLink :to="{ name: 'home' }">EnjoyTrip</RouterLink>
|
<RouterLink :to="{ name: 'home' }">EnjoyTrip</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="menuWrapper">
|
<div class="menuWrapper">
|
||||||
<div class="item"><RouterLink :to="{ name: 'home' }">검색</RouterLink></div>
|
<div class="item"><RouterLink :to="{ name: 'search' }">검색</RouterLink></div>
|
||||||
<div class="item"><RouterLink :to="{ name: 'board' }">게시판</RouterLink></div>
|
<div class="item"><RouterLink :to="{ name: 'board' }">게시판</RouterLink></div>
|
||||||
|
|
||||||
<template v-if="isLogin">
|
<template v-if="isLogin">
|
||||||
@ -53,10 +53,10 @@ header {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
box-shadow: 0px 4px 4px #0003;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
header a {
|
header a {
|
||||||
|
@ -8,11 +8,15 @@ const { type, primary } = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button :type="type" :class="primary ? 'primary' : ''">
|
<button :disabled="disabled" :type="type" :class="primary ? 'primary' : ''">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@ -31,6 +35,10 @@ button {
|
|||||||
transform 0.25s;
|
transform 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
button.primary {
|
button.primary {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
color: var(--color-background);
|
color: var(--color-background);
|
||||||
@ -46,10 +54,6 @@ button.primary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.primary:active {
|
button.primary:active {
|
||||||
background-color: var(--color-primary-soft);
|
background-color: var(--color-primary-soft);
|
||||||
}
|
}
|
||||||
|
27
src/components/common/KakaoMap.vue
Normal file
27
src/components/common/KakaoMap.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!-- eslint-disable no-undef -->
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const mapDiv = ref(null);
|
||||||
|
const map = ref(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const options = {
|
||||||
|
center: new kakao.maps.LatLng(33.450701, 126.570667),
|
||||||
|
level: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
map.value = new kakao.maps.Map(mapDiv.value, options);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="map" ref="mapDiv" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#map {
|
||||||
|
width: calc(100% - 400px);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
166
src/components/common/Select.vue
Normal file
166
src/components/common/Select.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<!-- eslint-disable vue/multi-word-component-names -->
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import FilledButton from './FilledButton.vue';
|
||||||
|
|
||||||
|
const { placeholder, options, optionName, disabled } = defineProps({
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '----',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
optionName: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
sidoCode: Number,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const updateModel = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const body = document.querySelector('body');
|
||||||
|
const buttonRef = ref(null);
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const menuStyle = ref('');
|
||||||
|
const selected = ref(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const rect = buttonRef.value.$el.getBoundingClientRect();
|
||||||
|
|
||||||
|
menuStyle.value = `transform: translate(${rect.left}px, ${rect.bottom + 4}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedName = computed(() => selected.value?.[optionName] ?? selected.value);
|
||||||
|
|
||||||
|
function handleOpen() {
|
||||||
|
isOpen.value = true;
|
||||||
|
body.style.pointerEvents = 'none';
|
||||||
|
body.style.overflow = 'hidden';
|
||||||
|
document.addEventListener('pointerdown', handleClose, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
isOpen.value = false;
|
||||||
|
body.removeAttribute('style');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose({ target }) {
|
||||||
|
if (target && target?.tagName !== 'HTML') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(option) {
|
||||||
|
selected.value = option;
|
||||||
|
updateModel('update:modelValue', option);
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FilledButton
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="'select' + (isOpen ? ' active' : '')"
|
||||||
|
ref="buttonRef"
|
||||||
|
@mousedown="handleOpen"
|
||||||
|
>
|
||||||
|
<span>{{ selectedName ?? placeholder }}</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden=""
|
||||||
|
>
|
||||||
|
<path d="m6 9 6 6 6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</FilledButton>
|
||||||
|
<Teleport v-if="isOpen" to="body">
|
||||||
|
<div class="menu-wrapper" :style="menuStyle">
|
||||||
|
<ul class="menu">
|
||||||
|
<li
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.value"
|
||||||
|
@click.stop="handleSelect(option)"
|
||||||
|
:class="'menu-item' + (selected === option ? ' selected' : '')"
|
||||||
|
>
|
||||||
|
{{ option[optionName] ?? option }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes show {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, -8px, 0) scale3d(0.95, 0.95, 0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select svg {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
min-width: max-content;
|
||||||
|
pointer-events: auto;
|
||||||
|
will-change: transform;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
animation: show 0.25s;
|
||||||
|
padding: 4px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.selected {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
</style>
|
@ -8,11 +8,15 @@ const { type, primary } = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button :type="type" :class="primary ? 'primary' : ''">
|
<button :disabled="disabled" :type="type" :class="primary ? 'primary' : ''">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
90
src/components/search/AttractionItem.vue
Normal file
90
src/components/search/AttractionItem.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script setup>
|
||||||
|
const { place } = defineProps({
|
||||||
|
place: {
|
||||||
|
contentId: Number,
|
||||||
|
first_image: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
addr1: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
addr2: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
mapx: {
|
||||||
|
type: Number,
|
||||||
|
default: 33,
|
||||||
|
},
|
||||||
|
mapy: {
|
||||||
|
type: Number,
|
||||||
|
default: 126,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
console.log(place.contentId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button @click="handleClick" class="attraction">
|
||||||
|
<img :src="place.first_image" alt="image" />
|
||||||
|
<div class="info">
|
||||||
|
<h3 class="title">{{ place.title }}</h3>
|
||||||
|
<address>{{ place.addr1 }}</address>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.attraction {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); */
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background-color: var(--color-background);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[src='/'],
|
||||||
|
img[src=''] {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
152
src/components/search/SearchBar.vue
Normal file
152
src/components/search/SearchBar.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import TextButton from '../common/TextButton.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Select from '../common/Select.vue';
|
||||||
|
import { useAttractionStore } from '@/stores/attractionStore';
|
||||||
|
|
||||||
|
const contentTypeList = [
|
||||||
|
{ value: 12, name: '관광지' },
|
||||||
|
{ value: 14, name: '문화시설' },
|
||||||
|
{ value: 15, name: '축제공연행사' },
|
||||||
|
{ value: 25, name: '여행코스' },
|
||||||
|
{ value: 28, name: '레포츠' },
|
||||||
|
{ value: 32, name: '숙박' },
|
||||||
|
{ value: 38, name: '쇼핑' },
|
||||||
|
{ value: 39, name: '음식점' },
|
||||||
|
];
|
||||||
|
const { search } = useAttractionStore();
|
||||||
|
const sidoList = ref([]);
|
||||||
|
const gugunList = ref([]);
|
||||||
|
const sido = ref({ sidoCode: 0 });
|
||||||
|
const gugun = ref({ gugunCode: 0 });
|
||||||
|
const contentType = ref(contentTypeList[0]);
|
||||||
|
const keyword = ref('');
|
||||||
|
const searchQuery = computed(() => {
|
||||||
|
const { sidoCode } = sido.value;
|
||||||
|
const { gugunCode } = gugun.value;
|
||||||
|
const areaQuery = `sidoCode=${sidoCode}` + (sidoCode ? `&gugunCode=${gugunCode}` : '');
|
||||||
|
const keywordQuery = `title=${keyword.value}`;
|
||||||
|
|
||||||
|
return [areaQuery, keywordQuery].join('&');
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(sido, ({ sidoCode }) => {
|
||||||
|
console.log(`sido : ${sidoCode}`);
|
||||||
|
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
||||||
|
gugunList.value = data;
|
||||||
|
console.log(gugunList.value.length);
|
||||||
|
});
|
||||||
|
// TODO: API call
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sidoList.value.length === 0) {
|
||||||
|
axios.get('//localhost:8000/area/sido').then(({ data }) => {
|
||||||
|
sidoList.value = data;
|
||||||
|
});
|
||||||
|
// TODO: API call
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
search(searchQuery.value);
|
||||||
|
// TODO: API call;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="search-wrapper">
|
||||||
|
<nav class="select-wrapper">
|
||||||
|
<!-- <Dropdown v-model="sido" :options="sidoList" option-label="sidoName" placeholder="시/도">
|
||||||
|
</Dropdown> -->
|
||||||
|
<Select placeholder="시/도" v-model="sido" optionName="sidoName" :options="sidoList" />
|
||||||
|
<Select
|
||||||
|
placeholder="시/군/구"
|
||||||
|
v-model="gugun"
|
||||||
|
:options="gugunList"
|
||||||
|
:disabled="gugunList.length === 0"
|
||||||
|
optionName="gugunName"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
placeholder="종류"
|
||||||
|
v-model="contentType"
|
||||||
|
:options="contentTypeList"
|
||||||
|
optionName="name"
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
|
<form @submit.prevent="handleSubmit" class="search-box">
|
||||||
|
<input type="text" name="keyword" v-model.lazy="keyword" placeholder="검색어를 입력하세요" />
|
||||||
|
<TextButton @click="handleSubmit">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-search"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
part="svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</TextButton>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 0 8px;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* .p-dropdown {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.p-dropdown-items {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.p-dropdown-item {
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
.p-dropdown-filter-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
} */
|
||||||
|
</style>
|
40
src/components/search/SideBar.vue
Normal file
40
src/components/search/SideBar.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useAttractionStore } from '@/stores/attractionStore';
|
||||||
|
import AttractionItem from './AttractionItem.vue';
|
||||||
|
import SearchBar from './SearchBar.vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const attractionStore = useAttractionStore();
|
||||||
|
const { attractionList } = storeToRefs(attractionStore);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="sidebar">
|
||||||
|
<SearchBar />
|
||||||
|
<ul class="resultList">
|
||||||
|
<li v-for="attraction in attractionList" :key="attraction.title">
|
||||||
|
<AttractionItem :place="attraction" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
@ -79,6 +79,17 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/search',
|
||||||
|
component: () => import('@/views/SearchView.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'search',
|
||||||
|
component: () => import('@/components/common/KakaoMap.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
16
src/stores/attractionStore.js
Normal file
16
src/stores/attractionStore.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { searchAttarctions } from '@/api/attraction';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const useAttractionStore = defineStore('attraction', () => {
|
||||||
|
const attractionList = ref([]);
|
||||||
|
|
||||||
|
function search(queryString) {
|
||||||
|
console.log(searchAttarctions);
|
||||||
|
searchAttarctions(queryString, ({ data }) => {
|
||||||
|
attractionList.value = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { attractionList, search };
|
||||||
|
});
|
24
src/views/SearchView.vue
Normal file
24
src/views/SearchView.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppHeader from '@/components/AppHeader.vue';
|
||||||
|
import KakaoMap from '@/components/common/KakaoMap.vue';
|
||||||
|
import SideBar from '@/components/search/SideBar.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppHeader />
|
||||||
|
<main>
|
||||||
|
<SideBar />
|
||||||
|
<KakaoMap />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user