Feat: Add attraction search page
This commit is contained in:
parent
74e38f2b6b
commit
a0c7cdc14f
@ -1,15 +1,10 @@
|
||||
import { localAxios } from "@/utils/http-commons";
|
||||
import { localAxios } from '@/utils/http-commons';
|
||||
|
||||
const local = localAxios;
|
||||
|
||||
function getArticles(contentTypeId, sidoCode, gugunCode, title, success, fail) {
|
||||
function searchAttarctions(queryString, success, fail) {
|
||||
//list
|
||||
local
|
||||
.get(
|
||||
`/attraction/search?contentTypeId=${contentTypeId}&sidoCode=${sidoCode}&gugunCode=${gugunCode}&title=${title}`
|
||||
)
|
||||
.then(success)
|
||||
.catch(fail);
|
||||
local.get(`/attraction/search?contentTypeId=12&${queryString}`).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-dark-1: var(--vt-c-white);
|
||||
--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 */
|
||||
@ -118,6 +120,10 @@ textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
*::placeholder {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre;
|
||||
}
|
||||
|
@ -21,10 +21,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
width: calc(100% - 400px);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
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>
|
@ -1,18 +1,40 @@
|
||||
<script setup></script>
|
||||
<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">asdf</div>
|
||||
<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 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--color-background);
|
||||
z-index: 99;
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.resultList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 0 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
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 };
|
||||
});
|
@ -7,17 +7,18 @@ import SideBar from '@/components/search/SideBar.vue';
|
||||
<template>
|
||||
<AppHeader />
|
||||
<main>
|
||||
<KakaoMap />
|
||||
<SideBar />
|
||||
<KakaoMap />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 80px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user