Feat: Add infinite scroll in attraction list

This commit is contained in:
jhynsoo 2024-05-21 15:11:48 +09:00
parent f05243e7e3
commit 9f340ee6ef
5 changed files with 67 additions and 101 deletions

View File

@ -31,7 +31,10 @@ onMounted(() => {
setTarget(newMarkers[0].getPosition()); setTarget(newMarkers[0].getPosition());
}); });
watch(target, (newTarget) => { watch(target, (newTarget, oldTarget) => {
if (oldTarget && newTarget.La === oldTarget.La && newTarget.Ma === oldTarget.Ma) {
return;
}
map.panTo(newTarget); map.panTo(newTarget);
}); });
}); });

View File

@ -1,65 +0,0 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({ currentPage: Number, totalPage: Number })
const emit = defineEmits(['pageChange'])
// const navigationSize = parseInt(import.meta.env.VITE_ARTICLE_NAVIGATION_SIZE)
const navigationSize = 5
const startPage = computed(() => {
return parseInt((props.currentPage - 1) / navigationSize) * navigationSize + 1
})
const endPage = computed(() => {
let lastPage =
parseInt((props.currentPage - 1) / navigationSize) * navigationSize + navigationSize
return props.totalPage < lastPage ? props.totalPage : lastPage
})
const endRange = computed(() => {
return parseInt((props.totalPage - 1) / navigationSize) * navigationSize < props.currentPage
})
function range(start, end) {
const list = []
for (let i = start; i <= end; i++) list.push(i)
return list
// return Array(end - start + 1)
// .fill()
// .map((val, i) => start + i);
}
function onPageChange(pg) {
console.log(pg + '로 이동!!!')
emit('pageChange', pg)
}
</script>
<template>
<div class="row">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" @click="onPageChange(1)">최신</a>
</li>
<li class="page-item">
<a class="page-link" @click="onPageChange(startPage == 1 ? 1 : startPage - 1)">이전</a>
</li>
<template v-for="pg in range(startPage, endPage)" :key="pg">
<li :class="currentPage === pg ? 'page-item active' : 'page-item'">
<a class="page-link" @click="onPageChange(pg)">{{ pg }}</a>
</li>
</template>
<li class="page-item">
<a class="page-link" @click="onPageChange(endRange ? totalPage : endPage + 1)">다음</a>
</li>
<li class="page-item"><a class="page-link" @click="onPageChange(totalPage)">마지막</a></li>
</ul>
</div>
</template>
<style scoped>
a {
cursor: pointer;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, ref, watch } from 'vue'; import { ref, watch } from 'vue';
import TextButton from '../common/TextButton.vue'; import TextButton from '../common/TextButton.vue';
import axios from 'axios'; import axios from 'axios';
import Select from '../common/Select.vue'; import Select from '../common/Select.vue';
@ -15,21 +15,13 @@ const contentTypeList = [
{ value: 38, name: '쇼핑' }, { value: 38, name: '쇼핑' },
{ value: 39, name: '음식점' }, { value: 39, name: '음식점' },
]; ];
const { search } = useAttractionStore(); const { search, setQueryString } = useAttractionStore();
const sidoList = ref([]); const sidoList = ref([]);
const gugunList = ref([]); const gugunList = ref([]);
const sido = ref({ sidoCode: 0 }); const sido = ref({ sidoCode: 0 });
const gugun = ref({ gugunCode: 0 }); const gugun = ref({ gugunCode: 0 });
const contentType = ref(contentTypeList[0]); const contentType = ref(contentTypeList[0]);
const keyword = ref(''); 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 }) => { watch(sido, ({ sidoCode }) => {
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => { axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
@ -38,13 +30,18 @@ watch(sido, ({ sidoCode }) => {
}); });
if (sidoList.value.length === 0) { if (sidoList.value.length === 0) {
axios.get('//localhost:8000/area/sido').then(({ data }) => { axios.get('//localhost:8000/area/sido').then(({ data }) => (sidoList.value = data));
sidoList.value = data;
});
} }
function handleSubmit() { function handleSubmit() {
search(searchQuery.value); const { sidoCode } = sido.value;
const { gugunCode } = gugun.value;
const areaQuery = `sidoCode=${sidoCode}` + (sidoCode ? `&gugunCode=${gugunCode}` : '');
const keywordQuery = `title=${keyword.value}`;
const queryString = [areaQuery, keywordQuery].join('&');
setQueryString(queryString);
search();
} }
</script> </script>
@ -127,19 +124,3 @@ button {
align-items: center; align-items: center;
} }
</style> </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

@ -3,9 +3,27 @@ import { useAttractionStore } from '@/stores/attractionStore';
import AttractionItem from './AttractionItem.vue'; import AttractionItem from './AttractionItem.vue';
import SearchBar from './SearchBar.vue'; import SearchBar from './SearchBar.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onMounted, onUnmounted, ref } from 'vue';
const attractionStore = useAttractionStore(); const attractionStore = useAttractionStore();
const { attractionList } = storeToRefs(attractionStore); const { attractionList, page, hasNextPage } = storeToRefs(attractionStore);
const observer = ref(null);
const sentinal = ref(null);
async function handleIntersect(entries) {
if (entries[0].isIntersecting && page.value && hasNextPage.value) {
await attractionStore.fetchNextPage();
}
}
onMounted(() => {
observer.value = new IntersectionObserver(handleIntersect, { threshold: 1 });
observer.value.observe(sentinal.value);
});
onUnmounted(() => {
observer.value.disconnect();
});
</script> </script>
<template> <template>
@ -15,6 +33,7 @@ const { attractionList } = storeToRefs(attractionStore);
<li v-for="attraction in attractionList" :key="attraction.title"> <li v-for="attraction in attractionList" :key="attraction.title">
<AttractionItem :place="attraction" /> <AttractionItem :place="attraction" />
</li> </li>
<li ref="sentinal"></li>
</ul> </ul>
</div> </div>
</template> </template>

View File

@ -4,17 +4,45 @@ import { ref } from 'vue';
export const useAttractionStore = defineStore('attraction', () => { export const useAttractionStore = defineStore('attraction', () => {
const attractionList = ref([]); const attractionList = ref([]);
const queryString = ref('');
const page = ref(0);
const hasNextPage = ref(true);
const target = ref(null); const target = ref(null);
function search(queryString) { function setQueryString(value) {
searchAttarctions(queryString, ({ data }) => { queryString.value = value;
attractionList.value = data; }
function search(preventClear = false) {
if (page.value === 0) {
page.value = 1;
}
const query = `${queryString.value}&page=${page.value}`;
searchAttarctions(query, ({ data }) => {
preventClear
? (attractionList.value = [...attractionList.value, ...data])
: (attractionList.value = data);
}); });
} }
function fetchNextPage() {
page.value += 1;
search(true);
}
function setTarget(coords) { function setTarget(coords) {
target.value = coords; target.value = coords;
} }
return { attractionList, target, setTarget, search }; return {
attractionList,
queryString,
target,
setTarget,
page,
hasNextPage,
setQueryString,
search,
fetchNextPage,
};
}); });