Feat: Add infinite scroll in attraction list
This commit is contained in:
parent
f05243e7e3
commit
9f340ee6ef
@ -31,7 +31,10 @@ onMounted(() => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import TextButton from '../common/TextButton.vue';
|
||||
import axios from 'axios';
|
||||
import Select from '../common/Select.vue';
|
||||
@ -15,21 +15,13 @@ const contentTypeList = [
|
||||
{ value: 38, name: '쇼핑' },
|
||||
{ value: 39, name: '음식점' },
|
||||
];
|
||||
const { search } = useAttractionStore();
|
||||
const { search, setQueryString } = 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 }) => {
|
||||
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
||||
@ -38,13 +30,18 @@ watch(sido, ({ sidoCode }) => {
|
||||
});
|
||||
|
||||
if (sidoList.value.length === 0) {
|
||||
axios.get('//localhost:8000/area/sido').then(({ data }) => {
|
||||
sidoList.value = data;
|
||||
});
|
||||
axios.get('//localhost:8000/area/sido').then(({ data }) => (sidoList.value = data));
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@ -127,19 +124,3 @@ button {
|
||||
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>
|
||||
|
@ -3,9 +3,27 @@ import { useAttractionStore } from '@/stores/attractionStore';
|
||||
import AttractionItem from './AttractionItem.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -15,6 +33,7 @@ const { attractionList } = storeToRefs(attractionStore);
|
||||
<li v-for="attraction in attractionList" :key="attraction.title">
|
||||
<AttractionItem :place="attraction" />
|
||||
</li>
|
||||
<li ref="sentinal"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -4,17 +4,45 @@ import { ref } from 'vue';
|
||||
|
||||
export const useAttractionStore = defineStore('attraction', () => {
|
||||
const attractionList = ref([]);
|
||||
const queryString = ref('');
|
||||
const page = ref(0);
|
||||
const hasNextPage = ref(true);
|
||||
const target = ref(null);
|
||||
|
||||
function search(queryString) {
|
||||
searchAttarctions(queryString, ({ data }) => {
|
||||
attractionList.value = data;
|
||||
function setQueryString(value) {
|
||||
queryString.value = value;
|
||||
}
|
||||
|
||||
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) {
|
||||
target.value = coords;
|
||||
}
|
||||
|
||||
return { attractionList, target, setTarget, search };
|
||||
return {
|
||||
attractionList,
|
||||
queryString,
|
||||
target,
|
||||
setTarget,
|
||||
page,
|
||||
hasNextPage,
|
||||
setQueryString,
|
||||
search,
|
||||
fetchNextPage,
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user