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;
|
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;
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#map {
|
#map {
|
||||||
position: absolute;
|
width: calc(100% - 400px);
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<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>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sidebar {
|
.sidebar {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 0;
|
flex-direction: column;
|
||||||
left: 0;
|
width: 400px;
|
||||||
width: 320px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
z-index: 99;
|
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resultList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
</style>
|
</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>
|
<template>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main>
|
<main>
|
||||||
<KakaoMap />
|
|
||||||
<SideBar />
|
<SideBar />
|
||||||
|
<KakaoMap />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
main {
|
main {
|
||||||
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 80px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100% - 80px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user