Feat: Add attraction search page

This commit is contained in:
jhynsoo 2024-05-20 16:47:44 +09:00
parent 74e38f2b6b
commit a0c7cdc14f
8 changed files with 302 additions and 23 deletions

View File

@ -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 };

View File

@ -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;
}

View File

@ -21,10 +21,7 @@ onMounted(() => {
<style scoped>
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
width: calc(100% - 400px);
height: 100%;
}
</style>

View 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>

View 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>

View File

@ -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>

View 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 };
});

View File

@ -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>